Compare commits

..

64 Commits

Author SHA1 Message Date
刘祥超
a8c8d80e3b 尝试根据端口号自动纠正源站地址中的scheme 2023-06-18 18:05:28 +08:00
刘祥超
c43b6b37ea 优化代码 2023-06-18 10:01:22 +08:00
刘祥超
ac2d57d2f1 同时设置Websocket允许来源域和防盗链时,以Websocket设置为优先 2023-06-16 09:56:37 +08:00
刘祥超
83ac62cda3 缓存条件增加"强制返回区间内容"选项 2023-06-15 15:14:06 +08:00
刘祥超
c0909a2cd0 部分WAF动作输出内容时增加自定义报头 2023-06-12 18:07:07 +08:00
刘祥超
a73b9f2674 版本号改为1.2.0 2023-06-12 14:43:07 +08:00
刘祥超
3e79b71afc WAF在输出内容时也加入自定义的响应报头 2023-06-11 10:46:20 +08:00
刘祥超
d3caccbb55 上传日志时检查节点ID是否为0 2023-06-10 16:47:27 +08:00
刘祥超
f95bac8d38 在Linux上不通过交叉编译器编译时,也可以支持边缘脚本(在有商业版本源码的情况下) 2023-06-10 15:16:06 +08:00
刘祥超
41d2ab728b 手动发送数据(Send()方法)时也可以使用HTTP Header策略等 2023-06-09 14:49:32 +08:00
刘祥超
b319061e85 优化OSS相关代码 2023-06-08 17:47:04 +08:00
刘祥超
99b8686a49 修复部分测试用例 2023-06-07 21:49:42 +08:00
刘祥超
fe8c5b505a 修复一处编译问题 2023-06-07 20:30:52 +08:00
刘祥超
f88d0982ed 修复User-Agent为空时,使用了默认的Go-http-client/1.1的问题 2023-06-07 20:17:07 +08:00
刘祥超
a9389d53e1 优化代码 2023-06-07 19:30:51 +08:00
刘祥超
fc4b45fec7 HTTP服务反向代理时只把HTTP(S)源站加入到状态管理中 2023-06-07 19:28:16 +08:00
刘祥超
9b22e6cf69 初步实现对象存储源站 2023-06-07 17:27:55 +08:00
刘祥超
7bd7f7da45 允许在集群设置 -- “网站设置” 中设置节点IP访问显示的内容 2023-06-05 19:28:01 +08:00
刘祥超
b68e6517df 网站全局设置增加“强制Ln请求“选项 2023-06-05 17:06:03 +08:00
刘祥超
bbae229d08 优化Ln连接性能 2023-06-05 16:38:29 +08:00
刘祥超
c73a6cbfe8 节点监控数据增加UDP数据报速率 2023-06-04 09:58:43 +08:00
刘祥超
e869e8e4d6 缓存写入Header时忽略Strict-Transport-Security和Alt-Svc 2023-06-02 15:23:54 +08:00
刘祥超
bde4e8507f 优化代码 2023-06-02 14:23:54 +08:00
刘祥超
5ae25cffa0 连接列表增加udp支持 2023-06-02 10:54:17 +08:00
刘祥超
44b721d0d3 优化代码 2023-06-01 19:40:15 +08:00
刘祥超
a2d6b7e0a8 初步实现HTTP3 2023-06-01 17:49:06 +08:00
刘祥超
95d65481e3 优化代码 2023-05-29 20:39:08 +08:00
刘祥超
71522243b5 WAF增加“跳转”动作 2023-05-28 17:11:33 +08:00
刘祥超
953533d9a3 版本号改为1.1.0 2023-05-28 16:07:09 +08:00
刘祥超
31509392c9 Update http_request_host_redirect.go 2023-05-27 21:01:55 +08:00
刘祥超
dc30469b2c WAF区域封禁增加文字提示 2023-05-27 20:32:03 +08:00
刘祥超
fa2903ebb9 解析正则表达式关键词时限制组合的关键词数量不超过32个 2023-05-27 11:40:19 +08:00
刘祥超
c43387bf6a WAF国家/地区封禁、省份封禁增加例外URL、限制URL 2023-05-25 12:02:40 +08:00
刘祥超
47f5cbeac9 网站全局设置中增加“自动匹配证书”选项 2023-05-24 17:20:52 +08:00
刘祥超
2aea68fac4 某个网站找不到证书的情况下不再自动匹配证书 2023-05-24 17:00:27 +08:00
刘祥超
9f7e6559d3 实现集群CC防护策略设置 2023-05-23 19:17:13 +08:00
刘祥超
e352e3125c 实现集群自定义页面 2023-05-22 17:31:26 +08:00
刘祥超
6b5e93f5ad 更新UserAgent分析库 2023-05-20 11:52:27 +08:00
刘祥超
cea5ae9c6c 优化UserAgent内存 2023-05-20 10:38:17 +08:00
刘祥超
80b2bbf1eb 修复UserAgent分析可能产生的死锁 2023-05-19 20:11:53 +08:00
刘祥超
c87f25d7bf HTTP Header中支持设置非标Header 2023-05-19 19:52:04 +08:00
刘祥超
82aff804a6 ${response.header.NAME}变量中的NAME可以是非标准格式 2023-05-19 18:10:20 +08:00
刘祥超
973e67acdb ${header.NAME}变量中的NAME可以时非标准格式 2023-05-19 17:50:39 +08:00
刘祥超
93bd9daf76 HTTP Header - CORS跨域设置增加多个选项 2023-05-19 16:34:41 +08:00
刘祥超
d8c121662c CORS默认允许的请求方法增加PATCH 2023-05-19 15:41:47 +08:00
刘祥超
5f33249c5d 监控数据增加整体流量数据 2023-05-17 10:48:59 +08:00
刘祥超
1beafc9976 防盗链增加”同时检查Origin选项“ 2023-05-02 17:06:24 +08:00
刘祥超
b3857adc0f 优化代码 2023-04-26 08:35:07 +08:00
刘祥超
e75010692c 加快ttlcache GC速度 2023-04-25 19:10:37 +08:00
刘祥超
f2df4a1560 完善测试用例 2023-04-25 17:38:59 +08:00
刘祥超
7a4b68de97 增加ttlcache默认最大容量 2023-04-25 17:24:05 +08:00
刘祥超
6fce430469 更新相关库 2023-04-24 21:07:42 +08:00
刘祥超
8856094dd1 更新go.mod 2023-04-24 21:03:48 +08:00
刘祥超
20bee16d28 版本号修改为1.0.4 2023-04-24 10:17:55 +08:00
刘祥超
b1cd971a21 缓存索引数据库加载失败时自动尝试重建数据库文件 2023-04-21 17:38:31 +08:00
刘祥超
d976a39711 环路(127.0.0.1)请求也统计带宽 2023-04-21 15:08:44 +08:00
刘祥超
accd0236ea 优化统计相关代码 2023-04-19 22:15:57 +08:00
刘祥超
fc401a1426 限制单次处理的服务城市数量 2023-04-19 22:06:39 +08:00
刘祥超
7e8c09a684 优化nftables集合元素过期时间判断 2023-04-19 13:20:31 +08:00
刘祥超
37ddff86f1 优化IP名单同步速度 2023-04-19 12:01:02 +08:00
刘祥超
4dc25fb71e 优化请求统计 2023-04-18 15:07:22 +08:00
刘祥超
42883fbe22 优化可用内存检查 2023-04-11 18:51:56 +08:00
刘祥超
a88d9a07be 版本号改为1.1.0 2023-04-11 18:51:47 +08:00
刘祥超
544f1e482a 版本号修改为1.0.1 2023-04-10 09:18:45 +08:00
98 changed files with 1857 additions and 696 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*_plus.go
*_plus_test.go
*-plus.sh

View File

@@ -55,7 +55,7 @@ function build() {
cp -R "$ROOT"/pages "$DIST"/
# we support TOA on linux/amd64 only
if [ "$OS" == "linux" -a "$ARCH" == "amd64" ]
if [ "$OS" == "linux" ] && [ "$ARCH" == "amd64" ]
then
cp -R "$ROOT"/edge-toa "$DIST"
fi
@@ -114,7 +114,10 @@ function build() {
if [ ! -z $CC_PATH ]; then
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
else
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
BUILD_TAG="plus,script"
fi
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
fi
# delete hidden files

9
build/test.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
TAG=${1}
if [ -z "$TAG" ]; then
TAG="community"
fi
go test -v ../... -tags=${TAG}

33
go.mod
View File

@@ -5,6 +5,7 @@ go 1.18
replace (
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4
github.com/google/nftables => github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4
)
require (
@@ -16,49 +17,43 @@ require (
github.com/fsnotify/fsnotify v1.5.1
github.com/go-redis/redis/v8 v8.11.5
github.com/golang/protobuf v1.5.2
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55
github.com/google/nftables v0.1.0
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5
github.com/klauspost/compress v1.15.8
github.com/klauspost/compress v1.16.5
github.com/mattn/go-sqlite3 v1.14.9
github.com/mdlayher/netlink v1.4.2
github.com/mdlayher/netlink v1.7.1
github.com/miekg/dns v1.1.43
github.com/mssola/user_agent v0.5.3
github.com/mssola/useragent v1.0.0
github.com/pires/go-proxyproto v0.6.1
github.com/shirou/gopsutil/v3 v3.22.2
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
golang.org/x/text v0.3.7
golang.org/x/image v0.7.0
golang.org/x/net v0.8.0
golang.org/x/sys v0.6.0
golang.org/x/text v0.9.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.1
rogchap.com/v8go v0.7.0
)
require (
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/webp v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/tools v0.1.8 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/sync v0.1.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
honnef.co/go/tools v0.2.2 // indirect
)

166
go.sum
View File

@@ -1,8 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
@@ -18,8 +16,6 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -28,9 +24,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -41,14 +36,11 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -70,18 +62,15 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6 h1:btadZscaRmsi/+fOhkyUguRpSnrf6dykNEWxDeUCj9I=
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6/go.mod h1:0F8on3JWMkm+xahTHItkiu/E1SPqMd0TOxNweQv8ptE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55 h1:shQNx0flJFBwKsGE7Hs3bI2bDz+YF0zl/4qE8B2KRiY=
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470 h1:TuRxvKRv9PxKVijWOkUnZm5TeanQqWGUJyPx9u6cra4=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
@@ -90,22 +79,14 @@ github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5 h1:SgKhrqRhWVpu0ZKxQM8MGjdhy7hlH3Xax8snwsZWSic=
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ=
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA=
github.com/klauspost/compress v1.15.8/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -116,31 +97,14 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
github.com/mdlayher/netlink v1.4.2 h1:3sbnJWe/LETovA7yRZIX3f9McVOWV3OySH6iIBxiFfI=
github.com/mdlayher/netlink v1.4.2/go.mod h1:13VaingaArGUTUxFLf/iEovKxXji32JAtF858jZYEug=
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY=
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578=
github.com/mssola/user_agent v0.5.3/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
@@ -165,26 +129,21 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -192,95 +151,61 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -323,8 +248,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
rogchap.com/v8go v0.7.0 h1:kgjbiO4zE5itA962ze6Hqmbs4HgZbGzmueCXsZtremg=
rogchap.com/v8go v0.7.0/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=

View File

@@ -226,6 +226,20 @@ func (this *FileListDB) Init() error {
err := this.hashMap.Load(this)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
// 自动修复错误
// TODO 将来希望能尽可能恢复以往数据库中的内容
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
_ = this.Close()
this.deleteDB()
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
err = this.Open(this.dbPath)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
} else {
_ = this.Init()
}
}
}
}()
@@ -683,3 +697,10 @@ func (this *FileListDB) shouldRecover() bool {
_ = result.Close()
return shouldRecover
}
// 删除数据库文件
func (this *FileListDB) deleteDB() {
_ = os.Remove(this.dbPath)
_ = os.Remove(this.dbPath + "-shm")
_ = os.Remove(this.dbPath + "-wal")
}

View File

@@ -12,6 +12,11 @@ import (
func TestFileListDB_ListLFUItems(t *testing.T) {
var db = caches.NewFileListDB()
defer func() {
_ = db.Close()
}()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
//err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
if err != nil {
@@ -22,10 +27,6 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
hashList, err := db.ListLFUItems(100)
if err != nil {
t.Fatal(err)
@@ -35,25 +36,38 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
var db = caches.NewFileListDB()
defer func() {
_ = db.Close()
}()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
if err != nil {
t.Fatal(err)
}
// wait transaction
time.Sleep(1 * time.Second)
}
func TestFileListDB_CleanMatchKey(t *testing.T) {
var db = caches.NewFileListDB()
defer func() {
_ = db.Close()
}()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
@@ -69,10 +83,16 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
var db = caches.NewFileListDB()
defer func() {
_ = db.Close()
}()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")

View File

@@ -59,15 +59,16 @@ func TestFileListHashMap_BigInt(t *testing.T) {
func TestFileListHashMap_Load(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
var m = caches.NewFileListHashMap()
var before = time.Now()
var db = list.GetDB("abc")

View File

@@ -5,6 +5,7 @@ package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
@@ -17,6 +18,11 @@ import (
func TestFileList_Init(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -29,6 +35,11 @@ func TestFileList_Init(t *testing.T) {
func TestFileList_Add(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -59,16 +70,21 @@ func TestFileList_Add(t *testing.T) {
}
func TestFileList_Add_Many(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
err := list.Init()
if err != nil {
t.Fatal(err)
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
var before = time.Now()
for i := 0; i < 10_000_000; i++ {
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
@@ -92,15 +108,15 @@ func TestFileList_Add_Many(t *testing.T) {
func TestFileList_Exist(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
total, _ := list.Count()
t.Log("total:", total)
@@ -130,7 +146,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
// 测试在多个数据库下的性能
var listSlice = []caches.ListInterface{}
for i := 1; i <= 10; i++ {
list := caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
var list = caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -138,6 +154,12 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
listSlice = append(listSlice, list)
}
defer func() {
for _, list := range listSlice {
_ = list.Close()
}
}()
var wg = sync.WaitGroup{}
var threads = 8
wg.Add(threads)
@@ -181,15 +203,16 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
func TestFileList_CleanPrefix(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
before := time.Now()
err = list.CleanPrefix("123")
if err != nil {
@@ -200,15 +223,15 @@ func TestFileList_CleanPrefix(t *testing.T) {
func TestFileList_Remove(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
list.OnRemove(func(item *caches.Item) {
t.Logf("remove %#v", item)
})
@@ -224,13 +247,15 @@ func TestFileList_Remove(t *testing.T) {
func TestFileList_Purge(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
var count = 0
_, err = list.Purge(caches.CountFileDB*2, func(hash string) error {
@@ -246,13 +271,15 @@ func TestFileList_Purge(t *testing.T) {
func TestFileList_PurgeLFU(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
err = list.IncreaseHit(stringutil.Md5("123456"))
if err != nil {
@@ -273,15 +300,16 @@ func TestFileList_PurgeLFU(t *testing.T) {
func TestFileList_Stat(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
stat, err := list.Stat(nil)
if err != nil {
t.Fatal(err)
@@ -291,6 +319,11 @@ func TestFileList_Stat(t *testing.T) {
func TestFileList_Count(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -305,7 +338,12 @@ func TestFileList_Count(t *testing.T) {
}
func TestFileList_CleanAll(t *testing.T) {
list := caches.NewFileList(Tea.Root + "/data")
var list = caches.NewFileList(Tea.Root + "/data")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -320,6 +358,11 @@ func TestFileList_CleanAll(t *testing.T) {
func TestFileList_IncreaseHit(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -333,7 +376,13 @@ func TestFileList_IncreaseHit(t *testing.T) {
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
for i := 0; i < 1000_000; i++ {
var count = 1_000_000
if !testutils.IsSingleTesting() {
count = 10
}
for i := 0; i < count; i++ {
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
}
if err != nil {
@@ -344,6 +393,11 @@ func TestFileList_IncreaseHit(t *testing.T) {
func TestFileList_UpgradeV3(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
@@ -363,6 +417,11 @@ func TestFileList_UpgradeV3(t *testing.T) {
func BenchmarkFileList_Exist(b *testing.B) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
b.Fatal(err)

View File

@@ -2,6 +2,7 @@ package caches
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/cespare/xxhash"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands"
@@ -107,7 +108,9 @@ func TestMemoryList_Purge_Large_List(t *testing.T) {
})
}
time.Sleep(1 * time.Hour)
if testutils.IsSingleTesting() {
time.Sleep(1 * time.Hour)
}
}
func TestMemoryList_Stat(t *testing.T) {
@@ -255,9 +258,11 @@ func TestMemoryList_GC(t *testing.T) {
//runtime.GC()
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
timeout := time.NewTimer(2 * time.Minute)
<-timeout.C
t.Log("2 minutes passed")
if testutils.IsSingleTesting() {
timeout := time.NewTimer(2 * time.Minute)
<-timeout.C
t.Log("2 minutes passed")
time.Sleep(30 * time.Minute)
time.Sleep(30 * time.Minute)
}
}

View File

@@ -4,6 +4,7 @@ package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"testing"
"time"
)
@@ -23,7 +24,9 @@ func TestNewOpenFileCache_Close(t *testing.T) {
cache.Get("d.txt")
cache.Close("a.txt")
time.Sleep(100 * time.Second)
if testutils.IsSingleTesting() {
time.Sleep(100 * time.Second)
}
}
func TestNewOpenFileCache_CloseAll(t *testing.T) {

View File

@@ -8,21 +8,29 @@ import (
)
func TestFileReader(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
}
_, path, _ := storage.keyPath("my-key")
fp, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
t.Log("file '" + path + "' not exists")
return
}
t.Fatal(err)
}
defer func() {
@@ -58,6 +66,10 @@ func TestFileReader_ReadHeader(t *testing.T) {
var path = "/Users/WorkSpace/EdgeProject/EdgeCache/p43/12/6b/126bbed90fc80f2bdfb19558948b0d49.cache"
fp, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
t.Log("'" + path + "' not exists")
return
}
t.Fatal(err)
}
defer func() {
@@ -66,6 +78,11 @@ func TestFileReader_ReadHeader(t *testing.T) {
var reader = NewFileReader(fp)
err = reader.Init()
if err != nil {
if os.IsNotExist(err) {
t.Log("file '" + path + "' not exists")
return
}
t.Fatal(err)
}
var buf = make([]byte, 16*1024)
@@ -79,13 +96,16 @@ func TestFileReader_ReadHeader(t *testing.T) {
}
func TestFileReader_Range(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -109,6 +129,10 @@ func TestFileReader_Range(t *testing.T) {
fp, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
t.Log("'" + path + "' not exists")
return
}
t.Fatal(err)
}
defer func() {

View File

@@ -899,7 +899,10 @@ func (this *FileStorage) Stop() {
memoryStorage.Stop()
})
_ = this.list.Reset()
if this.list != nil {
_ = this.list.Reset()
}
if this.purgeTicker != nil {
this.purgeTicker.Stop()
}
@@ -907,7 +910,9 @@ func (this *FileStorage) Stop() {
this.hotTicker.Stop()
}
_ = this.list.Close()
if this.list != nil {
_ = this.list.Close()
}
var openFileCache = this.openFileCache
if openFileCache != nil {

View File

@@ -18,7 +18,7 @@ import (
)
func TestFileStorage_Init(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
@@ -26,6 +26,8 @@ func TestFileStorage_Init(t *testing.T) {
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -44,13 +46,16 @@ func TestFileStorage_Init(t *testing.T) {
}
func TestFileStorage_OpenWriter(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -95,6 +100,9 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -123,13 +131,16 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
}
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -188,13 +199,16 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
}
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -243,13 +257,16 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
}
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -299,13 +316,16 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
}
func TestFileStorage_Read(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -335,13 +355,16 @@ func TestFileStorage_Read(t *testing.T) {
}
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -388,13 +411,16 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
}
func TestFileStorage_Read_NotFound(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -421,13 +447,16 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
}
func TestFileStorage_Delete(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -440,13 +469,16 @@ func TestFileStorage_Delete(t *testing.T) {
}
func TestFileStorage_Stat(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -465,13 +497,16 @@ func TestFileStorage_Stat(t *testing.T) {
}
func TestFileStorage_CleanAll(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -496,13 +531,16 @@ func TestFileStorage_CleanAll(t *testing.T) {
}
func TestFileStorage_Stop(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -518,6 +556,9 @@ func TestFileStorage_DecodeFile(t *testing.T) {
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
t.Fatal(err)
@@ -528,6 +569,9 @@ func TestFileStorage_DecodeFile(t *testing.T) {
func TestFileStorage_RemoveCacheFile(t *testing.T) {
var storage = NewFileStorage(nil)
defer storage.Stop()
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
}
@@ -536,13 +580,16 @@ func BenchmarkFileStorage_Read(b *testing.B) {
_ = utils.SetRLimit(1024 * 1024)
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
defer storage.Stop()
err := storage.Init()
if err != nil {
b.Fatal(err)

View File

@@ -3,6 +3,7 @@
package conns
import (
"github.com/iwind/TeaGo/types"
"net"
"sync"
)
@@ -10,14 +11,14 @@ import (
var SharedMap = NewMap()
type Map struct {
m map[string]map[int]net.Conn // ip => { port => Conn }
m map[string]map[string]net.Conn // ip => { network_port => Conn }
locker sync.RWMutex
}
func NewMap() *Map {
return &Map{
m: map[string]map[int]net.Conn{},
m: map[string]map[string]net.Conn{},
}
}
@@ -25,21 +26,19 @@ func (this *Map) Add(conn net.Conn) {
if conn == nil {
return
}
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
key, ip, ok := this.connAddr(conn)
if !ok {
return
}
var ip = tcpAddr.IP.String()
var port = tcpAddr.Port
this.locker.Lock()
defer this.locker.Unlock()
connMap, ok := this.m[ip]
if !ok {
this.m[ip] = map[int]net.Conn{port: conn}
this.m[ip] = map[string]net.Conn{key: conn}
} else {
connMap[port] = conn
connMap[key] = conn
}
}
@@ -47,14 +46,11 @@ func (this *Map) Remove(conn net.Conn) {
if conn == nil {
return
}
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
key, ip, ok := this.connAddr(conn)
if !ok {
return
}
var ip = tcpAddr.IP.String()
var port = tcpAddr.Port
this.locker.Lock()
defer this.locker.Unlock()
@@ -62,7 +58,7 @@ func (this *Map) Remove(conn net.Conn) {
if !ok {
return
}
delete(connMap, port)
delete(connMap, key)
if len(connMap) == 0 {
delete(this.m, ip)
@@ -121,3 +117,24 @@ func (this *Map) AllConns() []net.Conn {
return result
}
func (this *Map) connAddr(conn net.Conn) (key string, ip string, ok bool) {
if conn == nil {
return
}
var addr = conn.RemoteAddr()
switch realAddr := addr.(type) {
case *net.TCPAddr:
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
case *net.UDPAddr:
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
default:
var s = addr.String()
host, port, err := net.SplitHostPort(s)
if err != nil {
return
}
return addr.Network() + port, host, true
}
}

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "1.0.0"
Version = "1.2.0"
ProductName = "Edge Node"
ProcessName = "edge-node"

View File

@@ -88,11 +88,15 @@ type blockIPItem struct {
}
func NewNFTablesFirewall() (*NFTablesFirewall, error) {
conn, err := nftables.NewConn()
if err != nil {
return nil, err
}
var firewall = &NFTablesFirewall{
conn: nftables.NewConn(),
conn: conn,
dropIPQueue: make(chan *blockIPItem, 4096),
}
err := firewall.init()
err = firewall.init()
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
// +build linux
package nftables

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
// +build linux
package nftables_test
@@ -11,7 +10,10 @@ import (
)
func getIPv4Chain(t *testing.T) *nftables.Chain {
var conn = nftables.NewConn()
conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
if err != nil {
if err == nftables.ErrTableNotFound {

View File

@@ -15,10 +15,14 @@ type Conn struct {
rawConn *nft.Conn
}
func NewConn() *Conn {
return &Conn{
rawConn: &nft.Conn{},
func NewConn() (*Conn, error) {
conn, err := nft.New()
if err != nil {
return nil, err
}
return &Conn{
rawConn: conn,
}, nil
}
func (this *Conn) Raw() *nft.Conn {

View File

@@ -4,7 +4,10 @@
package nftables
import "errors"
import (
"errors"
"strings"
)
var ErrTableNotFound = errors.New("table not found")
var ErrChainNotFound = errors.New("chain not found")
@@ -15,5 +18,5 @@ func IsNotFound(err error) bool {
if err == nil {
return false
}
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound || strings.Contains(err.Error(), "no such file or directory")
}

View File

@@ -0,0 +1,65 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nftables
import (
"sync"
"time"
)
type Expiration struct {
m map[string]time.Time // key => expires time
lastGCAt int64
locker sync.RWMutex
}
func NewExpiration() *Expiration {
return &Expiration{
m: map[string]time.Time{},
}
}
func (this *Expiration) AddUnsafe(key []byte, expires time.Time) {
this.m[string(key)] = expires
}
func (this *Expiration) Add(key []byte, expires time.Time) {
this.locker.Lock()
this.m[string(key)] = expires
this.gc()
this.locker.Unlock()
}
func (this *Expiration) Remove(key []byte) {
this.locker.Lock()
delete(this.m, string(key))
this.locker.Unlock()
}
func (this *Expiration) Contains(key []byte) bool {
this.locker.RLock()
expires, ok := this.m[string(key)]
if ok && expires.Year() > 2000 && time.Now().After(expires) {
ok = false
}
this.locker.RUnlock()
return ok
}
func (this *Expiration) gc() {
// we won't gc too frequently
var currentTime = time.Now().Unix()
if this.lastGCAt >= currentTime {
return
}
this.lastGCAt = currentTime
var now = time.Now().Add(-10 * time.Second) // gc elements expired before 10 seconds ago
for key, expires := range this.m {
if expires.Year() > 2000 && now.After(expires) {
delete(this.m, key)
}
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nftables_test
import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"net"
"testing"
"time"
)
func TestExpiration_Add(t *testing.T) {
var expiration = nftables.NewExpiration()
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now())
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Time{})
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-1*time.Second))
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-10*time.Second))
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
expiration.Remove([]byte{'a', 'b', 'c'})
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
}
{
expiration.Add(net.ParseIP("10.254.0.75").To4(), time.Now())
t.Log(expiration.Contains(net.ParseIP("10.254.0.75").To4()))
}
}
func BenchmarkNewExpiration(b *testing.B) {
var expiration = nftables.NewExpiration()
for i := 0; i < 10_000; i++ {
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
}
})
}

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux
package nftables

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
// +build linux
package nftables
@@ -35,17 +34,25 @@ type Set struct {
conn *Conn
rawSet *nft.Set
batch *SetBatch
expiration *Expiration
}
func NewSet(conn *Conn, rawSet *nft.Set) *Set {
return &Set{
conn: conn,
rawSet: rawSet,
var set = &Set{
conn: conn,
rawSet: rawSet,
expiration: nil,
batch: &SetBatch{
conn: conn,
rawSet: rawSet,
},
}
// retrieve set elements to improve "delete" speed
set.initElements()
return set
}
func (this *Set) Raw() *nft.Set {
@@ -57,11 +64,21 @@ func (this *Set) Name() string {
}
func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool) error {
// check if already exists
if this.expiration != nil && !overwrite && this.expiration.Contains(key) {
return nil
}
var expiresTime = time.Time{}
var rawElement = nft.SetElement{
Key: key,
}
if options != nil {
rawElement.Timeout = options.Timeout
if options.Timeout > 0 {
expiresTime = time.UnixMilli(time.Now().UnixMilli() + options.Timeout.Milliseconds())
}
}
err := this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{
rawElement,
@@ -71,9 +88,19 @@ func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool)
}
err = this.conn.Commit()
if err != nil {
if err == nil {
if this.expiration != nil {
this.expiration.Add(key, expiresTime)
}
} else {
var isFileExistsErr = strings.Contains(err.Error(), "file exists")
if !overwrite && isFileExistsErr {
// ignore file exists error
return nil
}
// retry if exists
if overwrite && strings.Contains(err.Error(), "file exists") {
if overwrite && isFileExistsErr {
deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
{
Key: key,
@@ -85,6 +112,11 @@ func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool)
})
if err == nil {
err = this.conn.Commit()
if err == nil {
if this.expiration != nil {
this.expiration.Add(key, expiresTime)
}
}
}
}
}
@@ -107,6 +139,11 @@ func (this *Set) AddIPElement(ip string, options *ElementOptions, overwrite bool
}
func (this *Set) DeleteElement(key []byte) error {
// if set element does not exist, we return immediately
if this.expiration != nil && !this.expiration.Contains(key) {
return nil
}
err := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
{
Key: key,
@@ -116,9 +153,17 @@ func (this *Set) DeleteElement(key []byte) error {
return err
}
err = this.conn.Commit()
if err != nil {
if err == nil {
if this.expiration != nil {
this.expiration.Remove(key)
}
} else {
if strings.Contains(err.Error(), "no such file or directory") {
err = nil
if this.expiration != nil {
this.expiration.Remove(key)
}
}
}
return err

View File

@@ -1,4 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
package nftables

View File

@@ -0,0 +1,8 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux && !plus
package nftables
func (this *Set) initElements() {
// NOT IMPLEMENTED
}

View File

@@ -34,7 +34,7 @@ func getIPv4Set(t *testing.T) *nftables.Set {
func TestSet_AddElement(t *testing.T) {
var set = getIPv4Set(t)
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second})
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second}, false)
if err != nil {
t.Fatal(err)
}

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
// +build linux
package nftables

View File

@@ -1,6 +1,5 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build linux
// +build linux
package nftables_test
@@ -10,7 +9,10 @@ import (
)
func getIPv4Table(t *testing.T) *nftables.Table {
var conn = nftables.NewConn()
conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
if err != nil {
if err == nftables.ErrTableNotFound {

View File

@@ -17,13 +17,17 @@ import (
type IPListDB struct {
db *dbs.DB
itemTableName string
itemTableName string
versionTableName string
deleteExpiredItemsStmt *dbs.Stmt
deleteItemStmt *dbs.Stmt
insertItemStmt *dbs.Stmt
selectItemsStmt *dbs.Stmt
selectMaxVersionStmt *dbs.Stmt
deleteExpiredItemsStmt *dbs.Stmt
deleteItemStmt *dbs.Stmt
insertItemStmt *dbs.Stmt
selectItemsStmt *dbs.Stmt
selectMaxItemVersionStmt *dbs.Stmt
selectVersionStmt *dbs.Stmt
updateVersionStmt *dbs.Stmt
cleanTicker *time.Ticker
@@ -34,9 +38,10 @@ type IPListDB struct {
func NewIPListDB() (*IPListDB, error) {
var db = &IPListDB{
itemTableName: "ipItems",
dir: filepath.Clean(Tea.Root + "/data"),
cleanTicker: time.NewTicker(24 * time.Hour),
itemTableName: "ipItems",
versionTableName: "versions",
dir: filepath.Clean(Tea.Root + "/data"),
cleanTicker: time.NewTicker(24 * time.Hour),
}
err := db.init()
return db, err
@@ -108,6 +113,15 @@ ON "` + this.itemTableName + `" (
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"version" integer DEFAULT 0
);
`)
if err != nil {
return err
}
// 初始化SQL语句
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
if err != nil {
@@ -129,7 +143,20 @@ ON "` + this.itemTableName + `" (
return err
}
this.selectMaxVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
if err != nil {
return err
}
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
if err != nil {
return err
}
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
if err != nil {
return err
}
this.db = db
@@ -172,11 +199,15 @@ func (this *IPListDB) AddItem(item *pb.IPItem) error {
// 如果是删除,则不再创建新记录
if item.IsDeleted {
return nil
return this.UpdateMaxVersion(item.Version)
}
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
return err
if err != nil {
return err
}
return this.UpdateMaxVersion(item.Version)
}
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
@@ -210,27 +241,63 @@ func (this *IPListDB) ReadMaxVersion() int64 {
return 0
}
var row = this.selectMaxVersionStmt.QueryRow()
if row == nil {
return 0
// from version table
{
var row = this.selectVersionStmt.QueryRow()
if row == nil {
return 0
}
var version int64
err := row.Scan(&version)
if err == nil {
return version
}
}
var version int64
err := row.Scan(&version)
if err != nil {
return 0
// from items table
{
var row = this.selectMaxItemVersionStmt.QueryRow()
if row == nil {
return 0
}
var version int64
err := row.Scan(&version)
if err != nil {
return 0
}
return version
}
return version
}
// UpdateMaxVersion 修改版本号
func (this *IPListDB) UpdateMaxVersion(version int64) error {
if this.isClosed {
return nil
}
_, err := this.updateVersionStmt.Exec(version)
return err
}
func (this *IPListDB) Close() error {
this.isClosed = true
if this.db != nil {
_ = this.deleteExpiredItemsStmt.Close()
_ = this.deleteItemStmt.Close()
_ = this.insertItemStmt.Close()
_ = this.selectItemsStmt.Close()
_ = this.selectMaxVersionStmt.Close()
for _, stmt := range []*dbs.Stmt{
this.deleteExpiredItemsStmt,
this.deleteItemStmt,
this.insertItemStmt,
this.selectItemsStmt,
this.selectMaxItemVersionStmt, // ipItems table
this.selectVersionStmt, // versions table
this.updateVersionStmt,
} {
if stmt != nil {
_ = stmt.Close()
}
}
return this.db.Close()
}

View File

@@ -79,3 +79,15 @@ func TestIPListDB_ReadMaxVersion(t *testing.T) {
}
t.Log(db.ReadMaxVersion())
}
func TestIPListDB_UpdateMaxVersion(t *testing.T) {
db, err := iplibrary.NewIPListDB()
if err != nil {
t.Fatal(err)
}
err = db.UpdateMaxVersion(1027)
if err != nil {
t.Fatal(err)
}
t.Log(db.ReadMaxVersion())
}

View File

@@ -47,17 +47,20 @@ type IPListManager struct {
db *IPListDB
version int64
pageSize int64
lastVersion int64
fetchPageSize int64
listMap map[int64]*IPList
locker sync.Mutex
isFirstTime bool
}
func NewIPListManager() *IPListManager {
return &IPListManager{
pageSize: 1000,
listMap: map[int64]*IPList{},
fetchPageSize: 5_000,
listMap: map[int64]*IPList{},
isFirstTime: true,
}
}
@@ -117,11 +120,11 @@ func (this *IPListManager) init() {
_ = db.DeleteExpiredItems()
// 本地数据库中最大版本号
this.version = db.ReadMaxVersion()
this.lastVersion = db.ReadMaxVersion()
// 从本地数据库中加载
var offset int64 = 0
var size int64 = 1000
var size int64 = 2_000
for {
items, err := db.ReadItems(offset, size)
var l = len(items)
@@ -148,6 +151,11 @@ func (this *IPListManager) loop() error {
return nil
}
// 第一次同步则打印信息
if this.isFirstTime {
remotelogs.Println("IP_LIST_MANAGER", "initializing ip items ...")
}
for {
hasNext, err := this.fetch()
if err != nil {
@@ -159,6 +167,12 @@ func (this *IPListManager) loop() error {
time.Sleep(1 * time.Second)
}
// 第一次同步则打印信息
if this.isFirstTime {
this.isFirstTime = false
remotelogs.Println("IP_LIST_MANAGER", "finished initializing ip items")
}
return nil
}
@@ -168,8 +182,8 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
return false, err
}
itemsResp, err := rpcClient.IPItemRPC.ListIPItemsAfterVersion(rpcClient.Context(), &pb.ListIPItemsAfterVersionRequest{
Version: this.version,
Size: this.pageSize,
Version: this.lastVersion,
Size: this.fetchPageSize,
})
if err != nil {
if rpc.IsConnError(err) {
@@ -211,6 +225,7 @@ func (this *IPListManager) DeleteExpiredItems() {
}
}
// 处理IP条目
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
var changedLists = map[*IPList]zero.Zero{}
for _, item := range items {
@@ -280,8 +295,8 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
if fromRemote {
var latestVersion = items[len(items)-1].Version
if latestVersion > this.version {
this.version = latestVersion
if latestVersion > this.lastVersion {
this.lastVersion = latestVersion
}
}
}

View File

@@ -30,7 +30,6 @@ func TestIPListManager_check(t *testing.T) {
func TestIPListManager_loop(t *testing.T) {
manager := NewIPListManager()
manager.Start()
manager.pageSize = 10
err := manager.loop()
if err != nil {
t.Fatal(err)

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/metrics"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/rands"
"testing"
@@ -79,6 +80,10 @@ func TestTask_Add(t *testing.T) {
}
func TestTask_Add_Many(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
Id: 1,
IsOn: false,

View File

@@ -12,6 +12,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils"
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea"
@@ -34,6 +35,7 @@ type ClientConn struct {
hasRead bool
isLO bool // 是否为环路
isNoStat bool // 是否不统计带宽
isInAllowList bool
hasResetSYNFlood bool
@@ -53,17 +55,22 @@ type ClientConn struct {
func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool) net.Conn {
// 是否为环路
var remoteAddr = rawConn.RemoteAddr().String()
var isLO = strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:")
var conn = &ClientConn{
BaseClientConn: BaseClientConn{rawConn: rawConn},
isTLS: isTLS,
isHTTP: isHTTP,
isLO: isLO,
isLO: strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:"),
isNoStat: connutils.IsNoStatConn(remoteAddr),
isInAllowList: isInAllowList,
createdAt: time.Now().Unix(),
createdAt: fasttime.Now().Unix(),
}
if existsLnNodeIP(conn.RawIP()) {
conn.SetIsPersistent(true)
}
// 超时等设置
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil {
var performanceConfig = globalServerConfig.Performance
@@ -85,7 +92,7 @@ func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool
func (this *ClientConn) Read(b []byte) (n int, err error) {
if this.isDebugging {
this.lastReadAt = time.Now().Unix()
this.lastReadAt = fasttime.Now().Unix()
defer func() {
if err != nil {
@@ -127,7 +134,7 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
}
// 忽略白名单和局域网
if this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
if !this.isPersistent && this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
// SYN Flood检测
if this.serverId == 0 || !this.hasResetSYNFlood {
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
@@ -151,7 +158,7 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
}
if this.isDebugging {
this.lastWriteAt = time.Now().Unix()
this.lastWriteAt = fasttime.Now().Unix()
defer func() {
if err != nil {
@@ -163,8 +170,7 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
}
// 设置写超时时间
if this.autoWriteTimeout {
// TODO L2 -> L1 写入时不限制时间
if !this.isPersistent && this.autoWriteTimeout {
var timeoutSeconds = len(b) / 1024
if timeoutSeconds < 3 {
timeoutSeconds = 3
@@ -184,7 +190,7 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
// 统计当前服务带宽
if this.serverId > 0 {
// TODO 需要加入在serverId绑定之前的带宽
if !this.isLO || Tea.IsTesting() { // 环路不统计带宽,避免缓存预热等行为产生带宽
if !this.isNoStat || Tea.IsTesting() { // 环路不统计带宽,避免缓存预热等行为产生带宽
atomic.AddUint64(&teaconst.OutTrafficBytes, uint64(n))
var cost = time.Since(before).Seconds()
@@ -309,7 +315,7 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
_ = this.SetLinger(0)
_ = this.Close()
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, time.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击当前1分钟"+types.String(result)+"次空连接")
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, fasttime.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击当前1分钟"+types.String(result)+"次空连接")
}
}
}

View File

@@ -54,12 +54,13 @@ func (this *BaseClientConn) SetServerId(serverId int64) (goNext bool) {
goNext = true
// 检查服务相关IP黑名单
if serverId > 0 && len(this.rawIP) > 0 {
var rawIP = this.RawIP()
if serverId > 0 && len(rawIP) > 0 {
// 是否在白名单中
ok, _, expiresAt := iplibrary.AllowIP(this.rawIP, serverId)
ok, _, expiresAt := iplibrary.AllowIP(rawIP, serverId)
if !ok {
_ = this.rawConn.Close()
firewalls.DropTemporaryTo(this.rawIP, expiresAt)
firewalls.DropTemporaryTo(rawIP, expiresAt)
return false
}
}
@@ -123,8 +124,8 @@ func (this *BaseClientConn) TCPConn() (tcpConn *net.TCPConn, ok bool) {
switch conn := this.rawConn.(type) {
case *tls.Conn:
var internalConn = conn.NetConn()
clientConn, ok := internalConn.(*ClientConn)
if ok {
clientConn, isClientConn := internalConn.(*ClientConn)
if isClientConn {
return clientConn.TCPConn()
}
tcpConn, ok = internalConn.(*net.TCPConn)

View File

@@ -14,6 +14,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
"github.com/iwind/TeaGo/Tea"
"io"
"net"
@@ -61,7 +62,12 @@ func NewHTTPCacheTaskManager() *HTTPCacheTaskManager {
if err != nil {
return nil, err
}
return net.Dial(network, "127.0.0.1:"+port)
conn, err := net.Dial(network, "127.0.0.1:"+port)
if err != nil {
return nil, err
}
return connutils.NewNoStat(conn), nil
},
MaxIdleConns: 128,
MaxIdleConnsPerHost: 32,

View File

@@ -46,6 +46,7 @@ type HTTPRequest struct {
ServerAddr string // 实际启动的服务器监听地址
IsHTTP bool
IsHTTPS bool
IsHTTP3 bool
// 共享参数
nodeConfig *nodeconfigs.NodeConfig
@@ -479,6 +480,17 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
// remote addr
if web.RemoteAddr != nil && (web.RemoteAddr.IsPrior || isTop) && web.RemoteAddr.IsOn {
this.web.RemoteAddr = web.RemoteAddr
// check if from proxy
if len(this.web.RemoteAddr.Value) > 0 && this.web.RemoteAddr.Value != "${rawRemoteAddr}" {
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn != nil {
requestClientConn, ok := requestConn.(ClientConnInterface)
if ok {
requestClientConn.SetIsPersistent(true)
}
}
}
}
// charset
@@ -866,7 +878,23 @@ func (this *HTTPRequest) Format(source string) string {
}
switch suffix[:dotIndex] {
case "header":
return this.writer.Header().Get(suffix[dotIndex+1:])
var headers = this.writer.Header()
var headerKey = suffix[dotIndex+1:]
v, found := headers[headerKey]
if found {
if len(v) == 0 {
return ""
}
return v[0]
}
var canonicalHeaderKey = http.CanonicalHeaderKey(headerKey)
if canonicalHeaderKey != headerKey {
v = headers[canonicalHeaderKey]
if len(v) > 0 {
return v[0]
}
}
return ""
}
}
@@ -1288,6 +1316,12 @@ func (this *HTTPRequest) requestQueryParam(name string) string {
func (this *HTTPRequest) requestHeader(key string) string {
v, found := this.RawReq.Header[key]
if !found {
// 转换为canonical header再尝试
var canonicalHeaderKey = http.CanonicalHeaderKey(key)
if canonicalHeaderKey != key {
return strings.Join(this.RawReq.Header[canonicalHeaderKey], ";")
}
return ""
}
return strings.Join(v, ";")
@@ -1628,6 +1662,20 @@ func (this *HTTPRequest) processRequestHeaders(reqHeader http.Header) {
}
}
}
// 非标Header
if len(this.web.RequestHeaderPolicy.NonStandardHeaders) > 0 {
for _, name := range this.web.RequestHeaderPolicy.NonStandardHeaders {
var canonicalKey = http.CanonicalHeaderKey(name)
if canonicalKey != name {
v, ok := reqHeader[canonicalKey]
if ok {
delete(reqHeader, canonicalKey)
reqHeader[name] = v
}
}
}
}
}
}
@@ -1662,8 +1710,8 @@ func (this *HTTPRequest) fixRequestHeader(header http.Header) {
}
}
// 处理自定义Response Header
func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, statusCode int) {
// ProcessResponseHeaders 处理自定义Response Header
func (this *HTTPRequest) ProcessResponseHeaders(responseHeader http.Header, statusCode int) {
// 删除/添加/替换Header
// TODO 实现AddTrailers
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {
@@ -1727,8 +1775,22 @@ func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, stat
}
}
// 非标Header
if len(this.web.ResponseHeaderPolicy.NonStandardHeaders) > 0 {
for _, name := range this.web.ResponseHeaderPolicy.NonStandardHeaders {
var canonicalKey = http.CanonicalHeaderKey(name)
if canonicalKey != name {
v, ok := responseHeader[canonicalKey]
if ok {
delete(responseHeader, canonicalKey)
responseHeader[name] = v
}
}
}
}
// CORS
if this.web.ResponseHeaderPolicy.CORS != nil && this.web.ResponseHeaderPolicy.CORS.IsOn {
if this.web.ResponseHeaderPolicy.CORS != nil && this.web.ResponseHeaderPolicy.CORS.IsOn && (!this.web.ResponseHeaderPolicy.CORS.OptionsMethodOnly || this.RawReq.Method == http.MethodOptions) {
var corsConfig = this.web.ResponseHeaderPolicy.CORS
// Allow-Origin
@@ -1743,7 +1805,7 @@ func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, stat
// Allow-Methods
if len(corsConfig.AllowMethods) == 0 {
responseHeader.Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, HEAD, OPTIONS")
responseHeader.Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, HEAD, OPTIONS, PATCH")
} else {
responseHeader.Set("Access-Control-Allow-Methods", strings.Join(corsConfig.AllowMethods, ", "))
}
@@ -1753,6 +1815,16 @@ func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, stat
responseHeader.Set("Access-Control-Max-Age", types.String(corsConfig.MaxAge))
}
// Expose-Headers
if len(corsConfig.ExposeHeaders) > 0 {
responseHeader.Set("Access-Control-Expose-Headers", strings.Join(corsConfig.ExposeHeaders, ", "))
}
// Request-Method
if len(corsConfig.RequestMethod) > 0 {
responseHeader.Set("Access-Control-Request-Method", strings.ToUpper(corsConfig.RequestMethod))
}
// Allow-Credentials
responseHeader.Set("Access-Control-Allow-Credentials", "true")
}
@@ -1768,6 +1840,11 @@ func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, stat
this.ReqServer.HTTPS.SSLPolicy.HSTS.Match(this.ReqHost) {
responseHeader.Set(this.ReqServer.HTTPS.SSLPolicy.HSTS.HeaderKey(), this.ReqServer.HTTPS.SSLPolicy.HSTS.HeaderValue())
}
// HTTP/3
if this.IsHTTPS && !this.IsHTTP3 && this.ReqServer.SupportsHTTP3() {
this.processHTTP3Headers(responseHeader)
}
}
// 添加错误信息
@@ -1837,7 +1914,7 @@ func (this *HTTPRequest) canIgnore(err error) bool {
// 检查连接是否已关闭
func (this *HTTPRequest) isConnClosed() bool {
requestConn := this.RawReq.Context().Value(HTTPConnContextKey)
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return true
}

View File

@@ -40,7 +40,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var addStatusHeader = this.web.Cache.AddStatusHeader
if addStatusHeader {
defer func() {
cacheStatus := this.varMapping["cache.status"]
var cacheStatus = this.varMapping["cache.status"]
if cacheStatus != "HIT" {
this.writer.Header().Set("X-Cache", cacheStatus)
}
@@ -48,7 +48,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
}
// 检查服务独立的缓存条件
refType := ""
var refType = ""
for _, cacheRef := range this.web.Cache.CacheRefs {
if !cacheRef.IsOn {
continue
@@ -131,7 +131,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
this.varMapping["cache.key"] = key
// 读取缓存
storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
var storage = caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
if storage == nil {
this.cacheRef = nil
return
@@ -241,16 +241,19 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
reader, err = storage.OpenReader(key, useStale, false)
if err != nil && this.cacheRef.AllowPartialContent {
// 尝试读取分片的缓存内容
if len(rangeHeader) == 0 {
if len(rangeHeader) == 0 && this.cacheRef.ForcePartialContent {
// 默认读取开头
rangeHeader = "bytes=0-"
}
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader)
if pReader != nil {
isPartialCache = true
reader = pReader
partialRanges = ranges
err = nil
if len(rangeHeader) > 0 {
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader)
if pReader != nil {
isPartialCache = true
reader = pReader
partialRanges = ranges
err = nil
}
}
}
@@ -301,13 +304,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
this.writer.SetSentHeaderBytes(reader.HeaderSize())
var headerPool = this.bytePool(reader.HeaderSize())
var headerBuf = headerPool.Get()
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, err error) {
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, readErr error) {
headerData = append(headerData, headerBuf[:n]...)
for {
nIndex := bytes.Index(headerData, []byte{'\n'})
var nIndex = bytes.Index(headerData, []byte{'\n'})
if nIndex >= 0 {
row := headerData[:nIndex]
spaceIndex := bytes.Index(row, []byte{':'})
var row = headerData[:nIndex]
var spaceIndex = bytes.Index(row, []byte{':'})
if spaceIndex <= 0 {
return false, errors.New("invalid header '" + string(row) + "'")
}
@@ -375,7 +378,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 支持 If-None-Match
if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
// 自定义Header
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.addExpiresHeader(reader.ExpiresAt())
this.writer.WriteHeader(http.StatusNotModified)
this.isCached = true
@@ -387,7 +390,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 支持 If-Modified-Since
if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
// 自定义Header
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.addExpiresHeader(reader.ExpiresAt())
this.writer.WriteHeader(http.StatusNotModified)
this.isCached = true
@@ -396,7 +399,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
return true
}
this.processResponseHeaders(this.writer.Header(), reader.Status())
this.ProcessResponseHeaders(this.writer.Header(), reader.Status())
this.addExpiresHeader(reader.ExpiresAt())
// 返回上级节点过期时间
@@ -425,7 +428,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
if supportRange {
if len(rangeHeader) > 0 {
if fileSize == 0 {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -433,7 +436,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
if len(ranges) == 0 {
ranges, ok = httpRequestParseRangeHeader(rangeHeader)
if !ok {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -442,7 +445,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
for k, r := range ranges {
r2, ok := r.Convert(fileSize)
if !ok {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -460,9 +463,9 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var pool = this.bytePool(fileSize)
var bodyBuf = pool.Get()
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) {
_, err = this.writer.Write(bodyBuf[:n])
if err != nil {
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, readErr error) {
_, readErr = this.writer.Write(bodyBuf[:n])
if readErr != nil {
return false, errWritingToClient
}
return true, nil
@@ -472,7 +475,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
this.varMapping["cache.status"] = "MISS"
if err == caches.ErrInvalidRange {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -485,7 +488,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var boundary = httpRequestGenBoundary()
respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
respHeader.Del("Content-Length")
contentType := respHeader.Get("Content-Type")
var contentType = respHeader.Get("Content-Type")
this.writer.WriteHeader(http.StatusPartialContent)
@@ -516,9 +519,9 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var pool = this.bytePool(fileSize)
var bodyBuf = pool.Get()
err := reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, err error) {
_, err = this.writer.Write(bodyBuf[:n])
if err != nil {
err = reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, readErr error) {
_, readErr = this.writer.Write(bodyBuf[:n])
if readErr != nil {
return false, errWritingToClient
}
return true, nil

View File

@@ -57,7 +57,7 @@ func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage s
return "${" + varName + "}"
})
this.processResponseHeaders(this.writer.Header(), statusCode)
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(pageContent))
@@ -110,7 +110,7 @@ func (this *HTTPRequest) write50x(err error, statusCode int, enMessage string, z
return "${" + varName + "}"
})
this.processResponseHeaders(this.writer.Header(), statusCode)
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(pageContent))

View File

@@ -197,7 +197,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
// 响应Header
this.writer.AddHeaders(resp.Header)
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
// 准备
this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)

View File

@@ -17,7 +17,6 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
if this.web.MergeSlashes {
urlPath = utils.CleanPath(urlPath)
}
var fullURL = this.requestScheme() + "://" + this.ReqHost + urlPath
for _, u := range this.web.HostRedirects {
if !u.IsOn {
continue
@@ -35,10 +34,17 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
}
var fullURL = ""
if u.BeforeHasQuery() {
fullURL = this.URL()
} else {
fullURL = this.requestScheme() + "://" + this.ReqHost + urlPath
}
if len(u.Type) == 0 || u.Type == serverconfigs.HTTPHostRedirectTypeURL {
if u.MatchPrefix { // 匹配前缀
if strings.HasPrefix(fullURL, u.BeforeURL) {
afterURL := u.AfterURL
var afterURL = u.AfterURL
if u.KeepRequestURI {
afterURL += this.RawReq.URL.RequestURI()
}
@@ -48,7 +54,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
return false
}
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
return true
}
@@ -90,7 +96,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
}
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
return true
} else { // 精准匹配
@@ -104,11 +110,16 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
if u.KeepArgs {
var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 {
afterURL += this.uri[qIndex:]
var afterQIndex = strings.Index(u.AfterURL, "?")
if afterQIndex >= 0 {
afterURL = u.AfterURL[:afterQIndex] + this.uri[qIndex:]
} else {
afterURL += this.uri[qIndex:]
}
}
}
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
return true
}
@@ -144,7 +155,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
return false
}
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
// 参数
var qIndex = strings.Index(this.uri, "?")
@@ -200,7 +211,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
return false
}
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
return true
}

View File

@@ -0,0 +1,10 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import "net/http"
func (this *HTTPRequest) processHTTP3Headers(respHeader http.Header) {
// stub
}

View File

@@ -12,6 +12,10 @@ const (
LNExpiresHeader = "X-Edge-Ln-Expires"
)
func existsLnNodeIP(nodeIP string) bool {
return false
}
func (this *HTTPRequest) checkLnRequest() bool {
return false
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"net"
"net/http"
"time"
)
@@ -32,7 +33,14 @@ func (this *HTTPRequest) doMismatch() {
}
// 根据配置进行相应的处理
if sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
// 是否正在访问IP
if globalServerConfig.HTTPAll.NodeIPShowPage && net.ParseIP(this.ReqHost) != nil {
_, _ = this.writer.WriteString(globalServerConfig.HTTPAll.NodeIPPageHTML)
return
}
// 检查cc
// TODO 可以在管理端配置是否开启以及最多尝试次数
// 要考虑到服务在切换集群时,域名未生效状态时,用户访问的仍然是老集群中的节点,就会产生找不到域名的情况
@@ -47,7 +55,7 @@ func (this *HTTPRequest) doMismatch() {
}
// 处理当前连接
var httpAllConfig = sharedNodeConfig.GlobalServerConfig.HTTPAll
var httpAllConfig = globalServerConfig.HTTPAll
var mismatchAction = httpAllConfig.DomainMismatchAction
if mismatchAction != nil && mismatchAction.Code == "page" {
if mismatchAction.Options != nil {

View File

@@ -0,0 +1,15 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"net/http"
)
func (this *HTTPRequest) doOSSOrigin(origin *serverconfigs.OriginConfig) (resp *http.Response, goNext bool, errorCode string, err error) {
// stub
return nil, false, "", errors.New("not implemented")
}

View File

@@ -1,6 +1,7 @@
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
@@ -8,21 +9,44 @@ import (
"github.com/iwind/TeaGo/logs"
"net/http"
"os"
"regexp"
)
var urlPrefixRegexp = regexp.MustCompile("^(?i)(http|https|ftp)://")
// 请求特殊页面
func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
if len(this.web.Pages) == 0 {
// 集群自定义页面
if this.nodeConfig != nil && this.ReqServer != nil {
var httpPagesPolicy = this.nodeConfig.FindHTTPPagesPolicyWithClusterId(this.ReqServer.ClusterId)
if httpPagesPolicy != nil && httpPagesPolicy.IsOn && len(httpPagesPolicy.Pages) > 0 {
return this.doPageLookup(httpPagesPolicy.Pages, status)
}
}
return false
}
for _, page := range this.web.Pages {
// 查找当前网站自定义页面
shouldStop = this.doPageLookup(this.web.Pages, status)
if shouldStop {
return
}
// 集群自定义页面
if this.nodeConfig != nil && this.ReqServer != nil {
var httpPagesPolicy = this.nodeConfig.FindHTTPPagesPolicyWithClusterId(this.ReqServer.ClusterId)
if httpPagesPolicy != nil && httpPagesPolicy.IsOn && len(httpPagesPolicy.Pages) > 0 {
return this.doPageLookup(httpPagesPolicy.Pages, status)
}
}
return
}
func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, status int) (shouldStop bool) {
for _, page := range pages {
if page.Match(status) {
if len(page.BodyType) == 0 || page.BodyType == shared.BodyTypeURL {
if urlPrefixRegexp.MatchString(page.URL) {
if urlSchemeRegexp.MatchString(page.URL) {
var newStatus = page.NewStatus
if newStatus <= 0 {
newStatus = status
@@ -60,11 +84,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
// 修改状态码
if page.NewStatus > 0 {
// 自定义响应Headers
this.processResponseHeaders(this.writer.Header(), page.NewStatus)
this.ProcessResponseHeaders(this.writer.Header(), page.NewStatus)
this.writer.Prepare(nil, stat.Size(), page.NewStatus, true)
this.writer.WriteHeader(page.NewStatus)
} else {
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
this.writer.Prepare(nil, stat.Size(), status, true)
this.writer.WriteHeader(status)
}
@@ -99,11 +123,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
// 修改状态码
if page.NewStatus > 0 {
// 自定义响应Headers
this.processResponseHeaders(this.writer.Header(), page.NewStatus)
this.ProcessResponseHeaders(this.writer.Header(), page.NewStatus)
this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true)
this.writer.WriteHeader(page.NewStatus)
} else {
this.processResponseHeaders(this.writer.Header(), status)
this.ProcessResponseHeaders(this.writer.Header(), status)
this.writer.Prepare(nil, int64(len(content)), status, true)
this.writer.WriteHeader(status)
}

View File

@@ -12,7 +12,7 @@ func (this *HTTPRequest) doPlanExpires() {
this.tags = append(this.tags, "plan")
var statusCode = http.StatusNotFound
this.processResponseHeaders(this.writer.Header(), statusCode)
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode)
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))

View File

@@ -42,7 +42,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
}
var newURL = "https://" + host + this.RawReq.RequestURI
this.processResponseHeaders(this.writer.Header(), statusCode)
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
return true

View File

@@ -12,9 +12,32 @@ func (this *HTTPRequest) doCheckReferers() (shouldStop bool) {
return
}
var origin = this.RawReq.Header.Get("Origin")
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
// 处理用到Origin的特殊功能
if this.web.Referers.CheckOrigin && len(origin) > 0 {
// 处理Websocket
if this.web.Websocket != nil && this.web.Websocket.IsOn && this.RawReq.Header.Get("Upgrade") == "websocket" {
originHost, _ := httpParseHost(origin)
if len(originHost) > 0 && this.web.Websocket.MatchOrigin(originHost) {
return
}
}
}
var refererURL = this.RawReq.Header.Get("Referer")
if len(refererURL) == 0 && this.web.Referers.CheckOrigin {
if len(origin) > 0 && origin != "null" {
if urlSchemeRegexp.MatchString(origin) {
refererURL = origin
} else {
refererURL = "https://" + origin
}
}
}
if len(refererURL) == 0 {
if this.web.Referers.MatchDomain(this.ReqHost, "") {
return

View File

@@ -66,19 +66,21 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 二级节点
var hasMultipleLnNodes = false
if this.cacheRef != nil {
if this.cacheRef != nil || (this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && this.nodeConfig.GlobalServerConfig.HTTPAll.ForceLnRequest) {
origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds)
if origin != nil {
// 强制变更原来访问的域名
requestHost = this.ReqHost
}
// 回源Header中去除If-None-Match和If-Modified-Since
if !this.cacheRef.EnableIfNoneMatch {
this.DeleteHeader("If-None-Match")
}
if !this.cacheRef.EnableIfModifiedSince {
this.DeleteHeader("If-Modified-Since")
if this.cacheRef != nil {
// 回源Header中去除If-None-Match和If-Modified-Since
if !this.cacheRef.EnableIfNoneMatch {
this.DeleteHeader("If-None-Match")
}
if !this.cacheRef.EnableIfModifiedSince {
this.DeleteHeader("If-Modified-Since")
}
}
}
@@ -115,14 +117,20 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
requestHostHasVariables = origin.RequestHostHasVariables()
}
// 处理OSS
var isHTTPOrigin = origin.OSS == nil
// 处理Scheme
if origin.Addr == nil {
if isHTTPOrigin && origin.Addr == nil {
err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", err.Error())
this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true)
return
}
this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme()
if isHTTPOrigin {
this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme()
}
// StripPrefix
if len(stripPrefix) > 0 {
@@ -159,63 +167,66 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
this.uri = utils.CleanPath(this.uri)
}
// 获取源站地址
var originAddr = origin.Addr.PickAddress()
if origin.Addr.HostHasVariables() {
originAddr = this.Format(originAddr)
}
// 端口跟随
if origin.FollowPort {
var originHostIndex = strings.Index(originAddr, ":")
if originHostIndex < 0 {
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
return
var originAddr = ""
if isHTTPOrigin {
// 获取源站地址
originAddr = origin.Addr.PickAddress()
if origin.Addr.HostHasVariables() {
originAddr = this.Format(originAddr)
}
originAddr = originAddr[:originHostIndex+1] + types.String(this.requestServerPort())
}
this.originAddr = originAddr
// RequestHost
if len(requestHost) > 0 {
if requestHostHasVariables {
this.RawReq.Host = this.Format(requestHost)
// 端口跟随
if origin.FollowPort {
var originHostIndex = strings.Index(originAddr, ":")
if originHostIndex < 0 {
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
return
}
originAddr = originAddr[:originHostIndex+1] + types.String(this.requestServerPort())
}
this.originAddr = originAddr
// RequestHost
if len(requestHost) > 0 {
if requestHostHasVariables {
this.RawReq.Host = this.Format(requestHost)
} else {
this.RawReq.Host = requestHost
}
// 是否移除端口
if this.reverseProxy.RequestHostExcludingPort {
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
}
this.RawReq.URL.Host = this.RawReq.Host
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
// 源站主机名
var hostname = originAddr
if origin.Addr.Protocol.IsHTTPFamily() {
hostname = strings.TrimSuffix(hostname, ":80")
} else if origin.Addr.Protocol.IsHTTPSFamily() {
hostname = strings.TrimSuffix(hostname, ":443")
}
this.RawReq.Host = hostname
// 是否移除端口
if this.reverseProxy.RequestHostExcludingPort {
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
}
this.RawReq.URL.Host = this.RawReq.Host
} else {
this.RawReq.Host = requestHost
}
this.RawReq.URL.Host = this.ReqHost
// 是否移除端口
if this.reverseProxy.RequestHostExcludingPort {
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
}
this.RawReq.URL.Host = this.RawReq.Host
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
// 源站主机名
var hostname = originAddr
if origin.Addr.Protocol.IsHTTPFamily() {
hostname = strings.TrimSuffix(hostname, ":80")
} else if origin.Addr.Protocol.IsHTTPSFamily() {
hostname = strings.TrimSuffix(hostname, ":443")
}
this.RawReq.Host = hostname
// 是否移除端口
if this.reverseProxy.RequestHostExcludingPort {
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
}
this.RawReq.URL.Host = this.RawReq.Host
} else {
this.RawReq.URL.Host = this.ReqHost
// 是否移除端口
if this.reverseProxy.RequestHostExcludingPort {
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
this.RawReq.URL.Host = utils.ParseAddrHost(this.RawReq.URL.Host)
// 是否移除端口
if this.reverseProxy.RequestHostExcludingPort {
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
this.RawReq.URL.Host = utils.ParseAddrHost(this.RawReq.URL.Host)
}
}
}
@@ -241,34 +252,72 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
}
// 判断是否为Websocket请求
if this.RawReq.Header.Get("Upgrade") == "websocket" {
if isHTTPOrigin && this.RawReq.Header.Get("Upgrade") == "websocket" {
shouldRetry = this.doWebsocket(requestHost, isLastRetry)
return
}
// 获取请求客户端
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
if err != nil {
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
var resp *http.Response
var requestErr error
var requestErrCode string
if isHTTPOrigin { // 普通HTTP(S)源站
// 修复空User-Agent问题
_, existsUserAgent := this.RawReq.Header["User-Agent"]
if !existsUserAgent {
this.RawReq.Header["User-Agent"] = []string{""}
}
// 获取请求客户端
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
if err != nil {
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
return
}
// 尝试自动纠正源站地址中的scheme
if this.RawReq.URL.Scheme == "http" && strings.HasSuffix(originAddr, ":443") {
this.RawReq.URL.Scheme = "https"
} else if this.RawReq.URL.Scheme == "https" && strings.HasSuffix(originAddr, ":80") {
this.RawReq.URL.Scheme = "http"
}
// 开始请求
resp, requestErr = client.Do(this.RawReq)
} else if origin.OSS != nil { // OSS源站
var goNext bool
resp, goNext, requestErrCode, requestErr = this.doOSSOrigin(origin)
if requestErr == nil {
if resp == nil || !goNext {
return
}
}
} else {
this.writeCode(http.StatusBadGateway, "The type of origin site has not been supported", "设置的源站类型尚未支持")
return
}
// 开始请求
resp, err := client.Do(this.RawReq)
if err != nil {
if requestErr != nil {
// 客户端取消请求,则不提示
httpErr, ok := err.(*url.Error)
httpErr, ok := requestErr.(*url.Error)
if !ok {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling()
})
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+err.Error())
if isHTTPOrigin {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling()
})
}
if len(requestErrCode) > 0 {
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site (error code: "+requestErrCode+")", "源站读取失败(错误代号:"+requestErrCode+"", true)
} else {
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
}
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+requestErr.Error())
} else if httpErr.Err != context.Canceled {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling()
})
if isHTTPOrigin {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling()
})
}
// 是否需要重试
if (originId > 0 || (lnNodeId > 0 && hasMultipleLnNodes)) && !isLastRetry {
@@ -280,21 +329,21 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
}
if httpErr.Err != io.EOF {
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
}
return
}
if httpErr.Timeout() {
this.write50x(err, http.StatusGatewayTimeout, "Read origin site timeout", "源站读取超时", true)
this.write50x(requestErr, http.StatusGatewayTimeout, "Read origin site timeout", "源站读取超时", true)
} else if httpErr.Temporary() {
this.write50x(err, http.StatusServiceUnavailable, "Origin site unavailable now", "源站当前不可用", true)
this.write50x(requestErr, http.StatusServiceUnavailable, "Origin site unavailable now", "源站当前不可用", true)
} else {
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
}
if httpErr.Err != io.EOF {
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
}
} else {
// 是否为客户端方面的错误
@@ -314,7 +363,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
}
if !isClientError {
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
}
}
if resp != nil && resp.Body != nil {
@@ -337,7 +386,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
this.originStatus = int32(resp.StatusCode)
// 恢复源站状态
if !origin.IsOk {
if !origin.IsOk && isHTTPOrigin {
SharedOriginStateManager.Success(origin, func() {
this.reverseProxy.ResetScheduling()
})
@@ -346,7 +395,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// WAF对出站进行检查
if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn {
if this.doWAFResponse(resp) {
err = resp.Body.Close()
err := resp.Body.Close()
if err != nil {
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error())
}
@@ -356,7 +405,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 特殊页面
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
err = resp.Body.Close()
err := resp.Body.Close()
if err != nil {
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error())
}
@@ -409,7 +458,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 响应Header
this.writer.AddHeaders(resp.Header)
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
// 是否需要刷新
var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
@@ -437,6 +486,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 输出到客户端
var pool = this.bytePool(resp.ContentLength)
var buf = pool.Get()
var err error
if shouldAutoFlush {
for {
n, readErr := resp.Body.Read(buf)

View File

@@ -30,10 +30,10 @@ func (this *HTTPRequest) doRewrite() (shouldShop bool) {
// 跳转
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
if this.rewriteRule.RedirectStatus > 0 {
this.processResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
this.ProcessResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
} else {
this.processResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
}
return true

View File

@@ -217,7 +217,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
// 支持 If-None-Match
if this.requestHeader("If-None-Match") == eTag {
// 自定义Header
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.writer.WriteHeader(http.StatusNotModified)
return true
}
@@ -225,7 +225,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
// 支持 If-Modified-Since
if this.requestHeader("If-Modified-Since") == modifiedTime {
// 自定义Header
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
this.writer.WriteHeader(http.StatusNotModified)
return true
}
@@ -253,14 +253,14 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
var contentRange = this.RawReq.Header.Get("Range")
if len(contentRange) > 0 {
if fileSize == 0 {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
set, ok := httpRequestParseRangeHeader(contentRange)
if !ok {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -269,7 +269,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
for k, r := range ranges {
r2, ok := r.Convert(fileSize)
if !ok {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -290,7 +290,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
}
// 自定义Header
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
// 在Range请求中不能缓存
if len(ranges) > 0 {
@@ -325,7 +325,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
return true
}
if !ok {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}
@@ -377,7 +377,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
return true
}
if !ok {
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true
}

View File

@@ -19,7 +19,7 @@ func (this *HTTPRequest) doShutdown() {
if len(shutdown.BodyType) == 0 || shutdown.BodyType == shared.BodyTypeURL {
// URL
if urlPrefixRegexp.MatchString(shutdown.URL) {
if urlSchemeRegexp.MatchString(shutdown.URL) {
this.doURL(http.MethodGet, shutdown.URL, "", shutdown.Status, true)
return
}
@@ -28,10 +28,10 @@ func (this *HTTPRequest) doShutdown() {
if len(shutdown.URL) == 0 {
// 自定义响应Headers
if shutdown.Status > 0 {
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
this.writer.WriteHeader(shutdown.Status)
} else {
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK)
}
_, err := this.writer.WriteString("The site have been shutdown.")
@@ -59,10 +59,10 @@ func (this *HTTPRequest) doShutdown() {
// 自定义响应Headers
if shutdown.Status > 0 {
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
this.writer.WriteHeader(shutdown.Status)
} else {
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK)
}
buf := utils.BytePool1k.Get()
@@ -85,10 +85,10 @@ func (this *HTTPRequest) doShutdown() {
} else if shutdown.BodyType == shared.BodyTypeHTML {
// 自定义响应Headers
if shutdown.Status > 0 {
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
this.writer.WriteHeader(shutdown.Status)
} else {
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK)
}

View File

@@ -13,7 +13,7 @@ func (this *HTTPRequest) doTrafficLimit() {
this.tags = append(this.tags, "bandwidth")
var statusCode = 509
this.processResponseHeaders(this.writer.Header(), statusCode)
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode)
if len(config.NoticePageBody) != 0 {

View File

@@ -44,9 +44,9 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
// Header
if statusCode <= 0 {
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
} else {
this.processResponseHeaders(this.writer.Header(), statusCode)
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
}
if supportVariables {

View File

@@ -9,6 +9,7 @@ import (
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
@@ -22,6 +23,9 @@ var spiderRegexp = regexp.MustCompile(`(?i)(python|pycurl|http-client|httpclient
// 内容范围正则,其中的每个括号里的内容都在被引用,不能轻易修改
var contentRangeRegexp = regexp.MustCompile(`^bytes (\d+)-(\d+)/(\d+|\*)`)
// URL协议前缀
var urlSchemeRegexp = regexp.MustCompile("^(?i)(http|https|ftp)://")
// 分解Range
func httpRequestParseRangeHeader(rangeValue string) (result []rangeutils.Range, ok bool) {
// 参考RFChttps://tools.ietf.org/html/rfc7233
@@ -222,3 +226,16 @@ func httpRedirect(writer http.ResponseWriter, req *http.Request, url string, cod
http.Redirect(writer, req, url, code)
}
// 分析URL中的Host部分
func httpParseHost(urlString string) (host string, err error) {
if !urlSchemeRegexp.MatchString(urlString) {
urlString = "https://" + urlString
}
u, err := url.Parse(urlString)
if err != nil && u != nil {
return "", err
}
return u.Host, nil
}

View File

@@ -145,6 +145,23 @@ func TestHTTPRequest_httpRequestNextId_Concurrent(t *testing.T) {
a.IsTrue(countDuplicated == 0)
}
func TestHTTPParseURL(t *testing.T) {
for _, s := range []string{
"",
"null",
"example.com",
"https://example.com",
"https://example.com/hello",
} {
host, err := httpParseHost(s)
if err == nil {
t.Log(s, "=>", host)
} else {
t.Log(s, "=>")
}
}
}
func BenchmarkHTTPRequest_httpRequestNextId(b *testing.B) {
runtime.GOMAXPROCS(1)

View File

@@ -163,47 +163,52 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
// 检查地区封禁
if firewallPolicy.Mode == firewallconfigs.FirewallModeDefend {
if firewallPolicy.Inbound.Region != nil && firewallPolicy.Inbound.Region.IsOn {
regionConfig := firewallPolicy.Inbound.Region
var regionConfig = firewallPolicy.Inbound.Region
if regionConfig.IsNotEmpty() {
for _, remoteAddr := range remoteAddrs {
var result = iplib.LookupIP(remoteAddr)
if result != nil && result.IsOk() {
// 检查国家/地区级别封禁
var countryId = result.CountryId()
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
this.firewallPolicyId = firewallPolicy.Id
var currentURL = this.URL()
if regionConfig.MatchCountryURL(currentURL) {
// 检查国家/地区级别封禁
var countryId = result.CountryId()
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
this.firewallPolicyId = firewallPolicy.Id
this.writeCode(http.StatusForbidden, "", "")
this.writer.Flush()
this.writer.Close()
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
this.writer.Flush()
this.writer.Close()
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyCountry")
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyCountry")
}
return true, false
}
return true, false
}
// 检查省份封禁
var provinceId = result.ProvinceId()
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
this.firewallPolicyId = firewallPolicy.Id
if regionConfig.MatchProvinceURL(currentURL) {
// 检查省份封禁
var provinceId = result.ProvinceId()
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
this.firewallPolicyId = firewallPolicy.Id
this.writeCode(http.StatusForbidden, "", "")
this.writer.Flush()
this.writer.Close()
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
this.writer.Flush()
this.writer.Close()
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyProvince")
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyProvince")
}
return true, false
}
return true, false
}
}
}

View File

@@ -137,7 +137,7 @@ func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shou
return
}
this.processResponseHeaders(resp.Header, resp.StatusCode)
this.ProcessResponseHeaders(resp.Header, resp.StatusCode)
this.writer.statusCode = resp.StatusCode
// 将响应写回客户端

View File

@@ -362,7 +362,10 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
// 写入Header
var headerBuf = utils.SharedBufferPool.Get()
for k, v := range this.Header() {
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
if k == "Set-Cookie" ||
k == "Strict-Transport-Security" ||
k == "Alt-Svc" ||
(this.isPartial && k == "Content-Range") {
continue
}
for _, v1 := range v {
@@ -690,7 +693,10 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
// 写入Header
var headerBuffer = utils.SharedBufferPool.Get()
for k, v := range this.Header() {
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
if k == "Set-Cookie" ||
k == "Strict-Transport-Security" ||
k == "Alt-Svc" ||
(this.isPartial && k == "Content-Range") {
continue
}
for _, v1 := range v {
@@ -837,6 +843,14 @@ func (this *HTTPWriter) WriteHeader(statusCode int) {
// Send 直接发送内容,并终止请求
func (this *HTTPWriter) Send(status int, body string) {
this.req.ProcessResponseHeaders(this.Header(), status)
// content-length
_, hasContentLength := this.Header()["Content-Length"]
if !hasContentLength {
this.Header()["Content-Length"] = []string{types.String(len(body))}
}
this.WriteHeader(status)
_, _ = this.WriteString(body)
this.isFinished = true
@@ -882,6 +896,7 @@ func (this *HTTPWriter) SendResp(resp *http.Response) (int64, error) {
for k, v := range resp.Header {
this.SetHeader(k, v)
}
this.WriteHeader(resp.StatusCode)
var bufPool = this.req.bytePool(resp.ContentLength)
var buf = bufPool.Get()
@@ -1018,7 +1033,9 @@ func (this *HTTPWriter) finishWebP() {
if webpCacheWriter != nil {
// 写入Header
for k, v := range this.Header() {
if k == "Set-Cookie" {
if k == "Set-Cookie" ||
k == "Strict-Transport-Security" ||
k == "Alt-Svc" {
continue
}
@@ -1237,7 +1254,10 @@ func (this *HTTPWriter) finishRequest() {
// 计算Header长度
func (this *HTTPWriter) calculateHeaderLength() (result int) {
for k, v := range this.Header() {
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
if k == "Set-Cookie" ||
k == "Strict-Transport-Security" ||
k == "Alt-Svc" ||
(this.isPartial && k == "Content-Range") {
continue
}
for _, v1 := range v {

View File

@@ -91,10 +91,15 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
return nil, nil, errors.New("no configure found")
}
var globalServerConfig *serverconfigs.GlobalServerConfig
if sharedNodeConfig != nil {
globalServerConfig = sharedNodeConfig.GlobalServerConfig
}
// 如果域名为空,则取第一个
// 通常域名为空是因为是直接通过IP访问的
if len(domain) == 0 {
if group.IsHTTPS() && sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
if group.IsHTTPS() && globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
return nil, nil, errors.New("no tls server name matched")
}
@@ -111,23 +116,42 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
return nil, nil, errors.New("no tls server name found")
}
// 通过代理服务域名配置匹配
// 通过网站域名配置匹配
server, _ := this.findNamedServer(domain)
if server == nil || server.SSLPolicy() == nil || !server.SSLPolicy().IsOn {
if server == nil {
// 找不到或者此时的服务没有配置证书需要搜索所有的Server通过SSL证书内容中的DNSName匹配
// TODO 需要思考这种情况下是否允许访问
for _, server := range group.Servers() {
if server.SSLPolicy() == nil || !server.SSLPolicy().IsOn {
continue
}
cert, ok := server.SSLPolicy().MatchDomain(domain)
if ok {
return server.SSLPolicy(), cert, nil
// 此功能仅为了兼容以往版本v1.0.4),不应该作为常态启用
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchCertFromAllServers {
for _, searchingServer := range group.Servers() {
if searchingServer.SSLPolicy() == nil || !searchingServer.SSLPolicy().IsOn {
continue
}
cert, ok := searchingServer.SSLPolicy().MatchDomain(domain)
if ok {
return searchingServer.SSLPolicy(), cert, nil
}
}
}
return nil, nil, errors.New("no server found for '" + domain + "'")
}
if server.SSLPolicy() == nil || !server.SSLPolicy().IsOn {
// 找不到或者此时的服务没有配置证书需要搜索所有的Server通过SSL证书内容中的DNSName匹配
// 此功能仅为了兼容以往版本v1.0.4),不应该作为常态启用
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchCertFromAllServers {
for _, searchingServer := range group.Servers() {
if searchingServer.SSLPolicy() == nil || !searchingServer.SSLPolicy().IsOn {
continue
}
cert, ok := searchingServer.SSLPolicy().MatchDomain(domain)
if ok {
return searchingServer.SSLPolicy(), cert, nil
}
}
}
return nil, nil, errors.New("no cert found for '" + domain + "'")
}
// 证书是否匹配
var sslConfig = server.SSLPolicy()
@@ -150,19 +174,26 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
return
}
var matchDomainStrictly = sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
var matchDomainStrictly = globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly
if sharedNodeConfig.GlobalServerConfig != nil &&
len(sharedNodeConfig.GlobalServerConfig.HTTPAll.DefaultDomain) > 0 &&
(!matchDomainStrictly || configutils.MatchDomains(sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowMismatchDomains, name) || (sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowNodeIP && net.ParseIP(name) != nil)) {
var defaultDomain = sharedNodeConfig.GlobalServerConfig.HTTPAll.DefaultDomain
serverConfig, serverName = this.findNamedServerMatched(defaultDomain)
if serverConfig != nil {
if globalServerConfig != nil &&
len(globalServerConfig.HTTPAll.DefaultDomain) > 0 &&
(!matchDomainStrictly || configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) || (globalServerConfig.HTTPAll.AllowNodeIP && net.ParseIP(name) != nil)) {
if globalServerConfig.HTTPAll.AllowNodeIP &&
globalServerConfig.HTTPAll.NodeIPShowPage &&
net.ParseIP(name) != nil {
return
} else {
var defaultDomain = globalServerConfig.HTTPAll.DefaultDomain
serverConfig, serverName = this.findNamedServerMatched(defaultDomain)
if serverConfig != nil {
return
}
}
}
if matchDomainStrictly && !configutils.MatchDomains(sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowNodeIP || net.ParseIP(name) == nil) {
if matchDomainStrictly && !configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!globalServerConfig.HTTPAll.AllowNodeIP || net.ParseIP(name) == nil) {
return
}

View File

@@ -32,6 +32,7 @@ type HTTPListener struct {
addr string
isHTTP bool
isHTTPS bool
isHTTP3 bool
httpServer *http.Server
}
@@ -199,6 +200,7 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
ServerAddr: this.addr,
IsHTTP: this.isHTTP,
IsHTTPS: this.isHTTPS,
IsHTTP3: this.isHTTP3,
nodeConfig: sharedNodeConfig,
}

View File

@@ -36,9 +36,11 @@ func init() {
// ListenerManager 端口监听管理器
type ListenerManager struct {
listenersMap map[string]*Listener // addr => *Listener
locker sync.Mutex
lastConfig *nodeconfigs.NodeConfig
listenersMap map[string]*Listener // addr => *Listener
http3Listener *HTTPListener
locker sync.Mutex
lastConfig *nodeconfigs.NodeConfig
retryListenerMap map[string]*Listener // 需要重试的监听器 addr => Listener
ticker *time.Ticker
@@ -73,7 +75,7 @@ func NewListenerManager() *ListenerManager {
}
// Start 启动监听
func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
func (this *ListenerManager) Start(nodeConfig *nodeconfigs.NodeConfig) error {
this.locker.Lock()
defer this.locker.Unlock()
@@ -84,12 +86,12 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
/**if this.lastConfig != nil && this.lastConfig.Version == node.Version {
return nil
}**/
this.lastConfig = node
this.lastConfig = nodeConfig
// 所有的新地址
groupAddrs := []string{}
availableServerGroups := node.AvailableGroups()
if !node.IsOn {
var groupAddrs = []string{}
var availableServerGroups = nodeConfig.AvailableGroups()
if !nodeConfig.IsOn {
availableServerGroups = []*serverconfigs.ServerAddressGroup{}
}
@@ -98,13 +100,13 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
}
for _, group := range availableServerGroups {
addr := group.FullAddr()
var addr = group.FullAddr()
groupAddrs = append(groupAddrs, addr)
}
// 停掉老的
for listenerKey, listener := range this.listenersMap {
addr := listener.FullAddr()
var addr = listener.FullAddr()
if !lists.ContainsString(groupAddrs, addr) {
remotelogs.Println("LISTENER_MANAGER", "close '"+addr+"'")
_ = listener.Close()
@@ -115,7 +117,7 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
// 启动新的或修改老的
for _, group := range availableServerGroups {
addr := group.FullAddr()
var addr = group.FullAddr()
listener, ok := this.listenersMap[addr]
if ok {
// 不需要打印reload信息防止日志数量过多
@@ -129,7 +131,7 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
// 放入到重试队列中
this.retryListenerMap[addr] = listener
firstServer := group.FirstServer()
var firstServer = group.FirstServer()
if firstServer == nil {
remotelogs.Error("LISTENER_MANAGER", err.Error())
} else {
@@ -167,10 +169,15 @@ func (this *ListenerManager) TotalActiveConnections() int {
this.locker.Lock()
defer this.locker.Unlock()
total := 0
var total = 0
for _, listener := range this.listenersMap {
total += listener.listener.CountActiveConnections()
}
if this.http3Listener != nil {
total += this.http3Listener.CountActiveConnections()
}
return total
}
@@ -239,6 +246,17 @@ func (this *ListenerManager) addToFirewalld(groupAddrs []string) {
return
}
// HTTP/3相关端口
var http3Ports = sharedNodeConfig.FindHTTP3Ports()
if len(http3Ports) > 0 {
for _, port := range http3Ports {
var groupAddr = "udp://:" + types.String(port)
if !lists.ContainsString(groupAddrs, groupAddr) {
groupAddrs = append(groupAddrs, groupAddr)
}
}
}
// 组合端口号
var portStrings = []string{}
var udpPorts = []int{}
@@ -272,7 +290,9 @@ func (this *ListenerManager) addToFirewalld(groupAddrs []string) {
if newPortStrings == this.lastPortStrings {
return
}
this.locker.Lock()
this.lastPortStrings = newPortStrings
this.locker.Unlock()
remotelogs.Println("FIREWALLD", "opening ports automatically ...")
defer func() {
@@ -284,8 +304,10 @@ func (this *ListenerManager) addToFirewalld(groupAddrs []string) {
var udpPortRanges = utils.MergePorts(udpPorts)
defer func() {
this.locker.Lock()
this.lastTCPPortRanges = tcpPortRanges
this.lastUDPPortRanges = udpPortRanges
this.locker.Unlock()
}()
// 删除老的不存在的端口
@@ -321,3 +343,28 @@ func (this *ListenerManager) addToFirewalld(groupAddrs []string) {
_ = this.firewalld.AllowPortRangesPermanently(tcpPortRanges, "tcp")
_ = this.firewalld.AllowPortRangesPermanently(udpPortRanges, "udp")
}
func (this *ListenerManager) reloadFirewalld() {
this.locker.Lock()
defer this.locker.Unlock()
var nodeConfig = sharedNodeConfig
// 所有的新地址
var groupAddrs = []string{}
var availableServerGroups = nodeConfig.AvailableGroups()
if !nodeConfig.IsOn {
availableServerGroups = []*serverconfigs.ServerAddressGroup{}
}
if len(availableServerGroups) == 0 {
remotelogs.Println("LISTENER_MANAGER", "no available servers to startup")
}
for _, group := range availableServerGroups {
var addr = group.FullAddr()
groupAddrs = append(groupAddrs, addr)
}
go this.addToFirewalld(groupAddrs)
}

View File

@@ -693,6 +693,7 @@ func (this *Node) listenSock() error {
var lastReadAt int64
var lastWriteAt int64
var lastErrString = ""
var protocol = "tcp"
clientConn, ok := conn.(*ClientConn)
if ok {
createdAt = clientConn.CreatedAt()
@@ -703,6 +704,8 @@ func (this *Node) listenSock() error {
if lastErr != nil {
lastErrString = lastErr.Error()
}
} else {
protocol = "udp"
}
var age int64 = -1
var lastReadAge int64 = -1
@@ -719,6 +722,7 @@ func (this *Node) listenSock() error {
}
connMaps = append(connMaps, maps.Map{
"protocol": protocol,
"addr": conn.RemoteAddr().String(),
"age": age,
"readAge": lastReadAge,

View File

@@ -17,7 +17,9 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/net"
"golang.org/x/sys/unix"
"math"
"os"
"runtime"
"strings"
@@ -25,12 +27,17 @@ import (
)
type NodeStatusExecutor struct {
isFirstTime bool
isFirstTime bool
lastUpdatedTime time.Time
cpuUpdatedTime time.Time
cpuLogicalCount int
cpuPhysicalCount int
// 流量统计
lastIOCounterStat net.IOCountersStat
lastUDPInDatagrams int64
lastUDPOutDatagrams int64
apiCallStat *rpc.CallStat
ticker *time.Ticker
@@ -40,12 +47,15 @@ func NewNodeStatusExecutor() *NodeStatusExecutor {
return &NodeStatusExecutor{
ticker: time.NewTicker(30 * time.Second),
apiCallStat: rpc.NewCallStat(10),
lastUDPInDatagrams: -1,
lastUDPOutDatagrams: -1,
}
}
func (this *NodeStatusExecutor) Listen() {
this.isFirstTime = true
this.cpuUpdatedTime = time.Now()
this.lastUpdatedTime = time.Now()
this.update()
events.OnKey(events.EventQuit, this, func() {
@@ -119,6 +129,11 @@ func (this *NodeStatusExecutor) update() {
this.updateCacheSpace(status)
cacheSpaceTR.End()
this.updateAllTraffic(status)
// 修改更新时间
this.lastUpdatedTime = time.Now()
status.UpdatedAt = time.Now().Unix()
status.Timestamp = status.UpdatedAt
@@ -173,8 +188,6 @@ func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
})
if this.cpuLogicalCount == 0 && this.cpuPhysicalCount == 0 {
this.cpuUpdatedTime = time.Now()
status.CPULogicalCount, err = cpu.Counts(true)
if err != nil {
status.Error = "cpu.Counts(): " + err.Error()
@@ -282,3 +295,82 @@ func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus)
"dirs": result,
})
}
// 流量
func (this *NodeStatusExecutor) updateAllTraffic(status *nodeconfigs.NodeStatus) {
trafficCounters, err := net.IOCounters(true)
if err != nil {
remotelogs.Warn("NODE_STATUS_EXECUTOR", err.Error())
return
}
var allCounter = net.IOCountersStat{}
for _, counter := range trafficCounters {
// 跳过lo
if counter.Name == "lo" {
continue
}
allCounter.BytesRecv += counter.BytesRecv
allCounter.BytesSent += counter.BytesSent
}
if allCounter.BytesSent == 0 && allCounter.BytesRecv == 0 {
return
}
if this.lastIOCounterStat.BytesSent > 0 {
// 记录监控数据
if allCounter.BytesSent >= this.lastIOCounterStat.BytesSent && allCounter.BytesRecv >= this.lastIOCounterStat.BytesRecv {
var costSeconds = int(math.Ceil(time.Since(this.lastUpdatedTime).Seconds()))
if costSeconds > 0 {
var bytesSent = allCounter.BytesSent - this.lastIOCounterStat.BytesSent
var bytesRecv = allCounter.BytesRecv - this.lastIOCounterStat.BytesRecv
// UDP
var udpInDatagrams int64 = 0
var udpOutDatagrams int64 = 0
protoStats, protoErr := net.ProtoCounters([]string{"udp"})
if protoErr == nil {
for _, protoStat := range protoStats {
if protoStat.Protocol == "udp" {
udpInDatagrams = protoStat.Stats["InDatagrams"]
udpOutDatagrams = protoStat.Stats["OutDatagrams"]
if udpInDatagrams < 0 {
udpInDatagrams = 0
}
if udpOutDatagrams < 0 {
udpOutDatagrams = 0
}
}
}
}
var avgUDPInDatagrams int64 = 0
var avgUDPOutDatagrams int64 = 0
if this.lastUDPInDatagrams >= 0 && this.lastUDPOutDatagrams >= 0 {
avgUDPInDatagrams = (udpInDatagrams - this.lastUDPInDatagrams) / int64(costSeconds)
avgUDPOutDatagrams = (udpOutDatagrams - this.lastUDPOutDatagrams) / int64(costSeconds)
if avgUDPInDatagrams < 0 {
avgUDPInDatagrams = 0
}
if avgUDPOutDatagrams < 0 {
avgUDPOutDatagrams = 0
}
}
this.lastUDPInDatagrams = udpInDatagrams
this.lastUDPOutDatagrams = udpOutDatagrams
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAllTraffic, maps.Map{
"inBytes": bytesRecv,
"outBytes": bytesSent,
"avgInBytes": bytesRecv / uint64(costSeconds),
"avgOutBytes": bytesSent / uint64(costSeconds),
"avgUDPInDatagrams": avgUDPInDatagrams,
"avgUDPOutDatagrams": avgUDPOutDatagrams,
})
}
}
}
this.lastIOCounterStat = allCounter
}

View File

@@ -40,7 +40,7 @@ func (this *NodeStatusExecutor) updateMem(status *nodeconfigs.NodeStatus) {
if minFreeMemory > 1<<30 {
minFreeMemory = 1 << 30
}
if stat.Free < minFreeMemory {
if stat.Available > 0 && stat.Available < minFreeMemory {
runtime.GC()
debug.FreeOSMemory()
}

View File

@@ -80,6 +80,12 @@ func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
err = this.execUserServersStateChangedTask(rpcClient, task)
case "uamPolicyChanged":
err = this.execUAMPolicyChangedTask(rpcClient)
case "httpCCPolicyChanged":
err = this.execHTTPCCPolicyChangedTask(rpcClient)
case "http3PolicyChanged":
err = this.execHTTP3PolicyChangedTask(rpcClient)
case "httpPagesPolicyChanged":
err = this.execHTTPPagesPolicyChangedTask(rpcClient)
case "updatingServers":
err = this.execUpdatingServersTask(rpcClient)
case "plusChanged":
@@ -124,15 +130,6 @@ func (this *Node) execNodeVersionChangedTask() error {
return nil
}
// 脚本库变更
func (this *Node) execScriptsChangedTask() error {
err := this.reloadCommonScripts()
if err != nil {
return errors.New("reload common scripts failed: " + err.Error())
}
return nil
}
// 节点级别变更
func (this *Node) execNodeLevelChangedTask(rpcClient *rpc.RPCClient) error {
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(rpcClient.Context(), &pb.FindNodeLevelInfoRequest{})
@@ -159,34 +156,6 @@ func (this *Node) execNodeLevelChangedTask(rpcClient *rpc.RPCClient) error {
return nil
}
// UAM策略变更
func (this *Node) execUAMPolicyChangedTask(rpcClient *rpc.RPCClient) error {
remotelogs.Println("NODE", "updating uam policies ...")
resp, err := rpcClient.NodeRPC.FindNodeUAMPolicies(rpcClient.Context(), &pb.FindNodeUAMPoliciesRequest{})
if err != nil {
return err
}
var uamPolicyMap = map[int64]*nodeconfigs.UAMPolicy{}
for _, policy := range resp.UamPolicies {
if len(policy.UamPolicyJSON) > 0 {
var uamPolicy = &nodeconfigs.UAMPolicy{}
err = json.Unmarshal(policy.UamPolicyJSON, uamPolicy)
if err != nil {
remotelogs.Error("NODE", "decode uam policy failed: "+err.Error())
continue
}
err = uamPolicy.Init()
if err != nil {
remotelogs.Error("NODE", "initialize uam policy failed: "+err.Error())
continue
}
uamPolicyMap[policy.NodeClusterId] = uamPolicy
}
}
sharedNodeConfig.UpdateUAMPolicies(uamPolicyMap)
return nil
}
// DDoS配置变更
func (this *Node) execDDoSProtectionChangedTask(rpcClient *rpc.RPCClient) error {
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(rpcClient.Context(), &pb.FindNodeDDoSProtectionRequest{})

View File

@@ -0,0 +1,31 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import "github.com/TeaOSLab/EdgeNode/internal/rpc"
func (this *Node) execScriptsChangedTask() error {
// stub
return nil
}
func (this *Node) execUAMPolicyChangedTask(rpcClient *rpc.RPCClient) error {
// stub
return nil
}
func (this *Node) execHTTPCCPolicyChangedTask(rpcClient *rpc.RPCClient) error {
// stub
return nil
}
func (this *Node) execHTTP3PolicyChangedTask(rpcClient *rpc.RPCClient) error {
// stub
return nil
}
func (this *Node) execHTTPPagesPolicyChangedTask(rpcClient *rpc.RPCClient) error {
// stub
return nil
}

View File

@@ -164,6 +164,8 @@ func (this *Regexp) ParseKeywords(exp string) (keywords []string) {
return this.ParseKeywords(reg.Sub[0].String())
}
const maxComposedKeywords = 32
switch reg.Op {
case syntax.OpConcat:
var prevKeywords = []string{}
@@ -190,6 +192,11 @@ func (this *Regexp) ParseKeywords(exp string) (keywords []string) {
for _, prevKeyword := range prevKeywords {
for _, subKeyword := range subKeywords {
keywords = append(keywords, prevKeyword+subKeyword)
// 限制不能超出最大关键词
if len(keywords) > maxComposedKeywords {
return nil
}
}
}
prevKeywords = keywords

View File

@@ -58,59 +58,6 @@ func TestRegexp_ParseKeywords(t *testing.T) {
}
func TestRegexp_Special(t *testing.T) {
var unescape = func(v string) string {
//replace urlencoded characters
var chars = [][2]string{
{`\s`, `(\s|%09|%0A|\+)`},
{`\(`, `(\(|%28)`},
{`=`, `(=|%3D)`},
{`<`, `(<|%3C)`},
{`\*`, `(\*|%2A)`},
{`\\`, `(\\|%2F)`},
{`!`, `(!|%21)`},
{`/`, `(/|%2F)`},
{`;`, `(;|%3B)`},
{`\+`, `(\+|%20)`},
}
for _, c := range chars {
if !strings.Contains(v, c[0]) {
continue
}
var pieces = strings.Split(v, c[0])
// 修复piece中错误的\
for pieceIndex, piece := range pieces {
var l = len(piece)
if l == 0 {
continue
}
if piece[l-1] != '\\' {
continue
}
// 计算\的数量
var countBackSlashes = 0
for i := l - 1; i >= 0; i-- {
if piece[i] == '\\' {
countBackSlashes++
} else {
break
}
}
if countBackSlashes%2 == 1 {
// 去掉最后一个
pieces[pieceIndex] = piece[:len(piece)-1]
}
}
v = strings.Join(pieces, c[1])
}
return v
}
for _, s := range []string{
`\\s`,
`\s\W`,
@@ -121,7 +68,7 @@ func TestRegexp_Special(t *testing.T) {
`aaaa\\\=\W`,
`aaaa\\\\=\W`,
} {
var es = unescape(s)
var es = testUnescape(t, s)
t.Log(s, "=>", es)
_, err := re.Compile(es)
if err != nil {
@@ -130,6 +77,17 @@ func TestRegexp_Special(t *testing.T) {
}
}
func TestRegexp_Special2(t *testing.T) {
r, err := re.Compile(testUnescape(t, `/api/ios/a
/api/ios/b
/api/ios/c
/report`))
if err != nil {
t.Fatal(err)
}
t.Log(r.Keywords())
}
func TestRegexp_ParseKeywords2(t *testing.T) {
var a = assert.NewAssertion(t)
@@ -232,6 +190,14 @@ func BenchmarkRegexp_MatchString_VS_FindSubString2(b *testing.B) {
}
}
func TestSplitAndJoin(t *testing.T) {
var pieces = strings.Split(`/api/ios/a
/api/ios/b
/api/ios/c
/report`, "/")
t.Log(strings.Join(pieces, `(/|%2F)`))
}
func testCompareStrings(s1 []string, s2 []string) bool {
if len(s1) != len(s2) {
return false
@@ -243,3 +209,55 @@ func testCompareStrings(s1 []string, s2 []string) bool {
}
return true
}
func testUnescape(t *testing.T, v string) string {
// replace urlencoded characters
var unescapeChars = [][2]string{
{`\s`, `(\s|%09|%0A|\+)`},
{`\(`, `(\(|%28)`},
{`=`, `(=|%3D)`},
{`<`, `(<|%3C)`},
{`\*`, `(\*|%2A)`},
{`\\`, `(\\|%2F)`},
{`!`, `(!|%21)`},
{`/`, `(/|%2F)`},
{`;`, `(;|%3B)`},
{`\+`, `(\+|%20)`},
}
for _, c := range unescapeChars {
if !strings.Contains(v, c[0]) {
continue
}
var pieces = strings.Split(v, c[0])
// 修复piece中错误的\
for pieceIndex, piece := range pieces {
var l = len(piece)
if l == 0 {
continue
}
if piece[l-1] != '\\' {
continue
}
// 计算\的数量
var countBackSlashes = 0
for i := l - 1; i >= 0; i-- {
if piece[i] == '\\' {
countBackSlashes++
} else {
break
}
}
if countBackSlashes%2 == 1 {
// 去掉最后一个
pieces[pieceIndex] = piece[:len(piece)-1]
}
}
v = strings.Join(pieces, c[1])
}
return v
}

View File

@@ -289,6 +289,10 @@ Loop:
for {
select {
case log := <-logChan:
if log.NodeId <= 0 {
continue
}
// 是否已存在
var hash = xxhash.Sum64String(types.String(log.ServerId) + "_" + log.Description)
var found = false
@@ -312,6 +316,7 @@ Loop:
break Loop
}
}
if len(logList) == 0 {
return nil
}

View File

@@ -54,6 +54,9 @@ type HTTPRequestStatManager struct {
totalAttackRequests int64
locker sync.Mutex
monitorTicker *time.Ticker
uploadTicker *time.Ticker
}
// NewHTTPRequestStatManager 获取新对象
@@ -77,12 +80,12 @@ func NewHTTPRequestStatManager() *HTTPRequestStatManager {
// Start 启动
func (this *HTTPRequestStatManager) Start() {
// 上传请求总数
var monitorTicker = time.NewTicker(1 * time.Minute)
this.monitorTicker = time.NewTicker(1 * time.Minute)
events.OnKey(events.EventQuit, this, func() {
monitorTicker.Stop()
this.monitorTicker.Stop()
})
goman.New(func() {
for range monitorTicker.C {
for range this.monitorTicker.C {
if this.totalAttackRequests > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAttackRequests, maps.Map{"total": this.totalAttackRequests})
this.totalAttackRequests = 0
@@ -90,19 +93,19 @@ func (this *HTTPRequestStatManager) Start() {
}
})
var uploadTicker = time.NewTicker(30 * time.Minute)
this.uploadTicker = time.NewTicker(30 * time.Minute)
if Tea.IsTesting() {
uploadTicker = time.NewTicker(10 * time.Second) // 在测试环境下缩短Ticker时间以方便我们调试
this.uploadTicker = time.NewTicker(10 * time.Second) // 在测试环境下缩短Ticker时间以方便我们调试
}
remotelogs.Println("HTTP_REQUEST_STAT_MANAGER", "start ...")
events.OnKey(events.EventQuit, this, func() {
remotelogs.Println("HTTP_REQUEST_STAT_MANAGER", "quit")
uploadTicker.Stop()
this.uploadTicker.Stop()
})
// 上传Ticker
goman.New(func() {
for range uploadTicker.C {
for range this.uploadTicker.C {
var tr = trackers.Begin("UPLOAD_REQUEST_STATS")
err := this.Upload()
tr.End()
@@ -204,34 +207,38 @@ func (this *HTTPRequestStatManager) Loop() error {
if len(pieces) < 4 {
return nil
}
var serverId = pieces[0]
var serverIdString = pieces[0]
var ip = pieces[1]
var result = iplib.LookupIP(ip)
if result != nil && result.IsOk() {
var key = serverId + "@" + types.String(result.CountryId()) + "@" + types.String(result.ProvinceId()) + "@" + types.String(result.CityId())
this.locker.Lock()
stat, ok := this.cityMap[key]
if !ok {
// 检查数量
if this.serverCityCountMap[key] > 128 { // 限制单个服务的城市数量,防止数量过多
this.locker.Unlock()
return nil
}
this.serverCityCountMap[key]++ // 需要放在限制之后因为使用的是int16
if result.CountryId() > 0 {
var key = serverIdString + "@" + types.String(result.CountryId()) + "@" + types.String(result.ProvinceId()) + "@" + types.String(result.CityId())
stat, ok := this.cityMap[key]
if !ok {
// 检查数量
if this.serverCityCountMap[serverIdString] > 128 { // 限制单个服务的城市数量,防止数量过多
this.locker.Unlock()
return nil
}
this.serverCityCountMap[serverIdString]++ // 需要放在限制之后因为使用的是int16
stat = &StatItem{}
this.cityMap[key] = stat
}
stat.Bytes += types.Int64(pieces[2])
stat.CountRequests++
if types.Int8(pieces[3]) == 1 {
stat.AttackBytes += types.Int64(pieces[2])
stat.CountAttackRequests++
stat = &StatItem{}
this.cityMap[key] = stat
}
stat.Bytes += types.Int64(pieces[2])
stat.CountRequests++
if types.Int8(pieces[3]) == 1 {
stat.AttackBytes += types.Int64(pieces[2])
stat.CountAttackRequests++
}
}
if result.ProviderId() > 0 {
this.providerMap[serverId+"@"+types.String(result.ProviderId())]++
this.providerMap[serverIdString+"@"+types.String(result.ProviderId())]++
} else if utils.IsLocalIP(ip) { // 局域网IP
this.providerMap[serverIdString+"@258"]++
}
this.locker.Unlock()
}
@@ -240,7 +247,7 @@ func (this *HTTPRequestStatManager) Loop() error {
if atIndex < 0 {
return nil
}
var serverId = userAgentString[:atIndex]
var serverIdString = userAgentString[:atIndex]
var userAgent = userAgentString[atIndex+1:]
var result = SharedUserAgentParser.Parse(userAgent)
@@ -252,11 +259,11 @@ func (this *HTTPRequestStatManager) Loop() error {
}
this.locker.Lock()
var systemKey = serverId + "@" + osInfo.Name + "@" + osInfo.Version
var systemKey = serverIdString + "@" + osInfo.Name + "@" + osInfo.Version
_, ok := this.systemMap[systemKey]
if !ok {
if this.serverSystemCountMap[serverId] < 128 { // 限制最大数据,防止攻击
this.serverSystemCountMap[serverId]++
if this.serverSystemCountMap[serverIdString] < 128 { // 限制最大数据,防止攻击
this.serverSystemCountMap[serverIdString]++
ok = true
}
}
@@ -274,11 +281,11 @@ func (this *HTTPRequestStatManager) Loop() error {
}
this.locker.Lock()
var browserKey = serverId + "@" + browser + "@" + browserVersion
var browserKey = serverIdString + "@" + browser + "@" + browserVersion
_, ok := this.browserMap[browserKey]
if !ok {
if this.serverBrowserCountMap[serverId] < 256 { // 限制最大数据,防止攻击
this.serverBrowserCountMap[serverId]++
if this.serverBrowserCountMap[serverIdString] < 256 { // 限制最大数据,防止攻击
this.serverBrowserCountMap[serverIdString]++
ok = true
}
}
@@ -374,7 +381,7 @@ func (this *HTTPRequestStatManager) Upload() error {
sort.Slice(pbCities, func(i, j int) bool {
return pbCities[i].CountRequests > pbCities[j].CountRequests
})
var serverCountMap = map[int64]int16{}
var serverCountMap = map[int64]int16{} // serverId => count
for _, city := range pbCities {
serverCountMap[city.ServerId]++
if serverCountMap[city.ServerId] > maxCities {

View File

@@ -4,7 +4,8 @@ package stats
import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/mssola/user_agent"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/mssola/useragent"
"sync"
)
@@ -12,10 +13,10 @@ var SharedUserAgentParser = NewUserAgentParser()
// UserAgentParser UserAgent解析器
type UserAgentParser struct {
parser *user_agent.UserAgent
parser *useragent.UserAgent
cacheMap1 map[string]UserAgentParserResult
cacheMap2 map[string]UserAgentParserResult
cacheMap1 map[uint64]UserAgentParserResult
cacheMap2 map[uint64]UserAgentParserResult
maxCacheItems int
cacheCursor int
@@ -24,9 +25,9 @@ type UserAgentParser struct {
func NewUserAgentParser() *UserAgentParser {
var parser = &UserAgentParser{
parser: &user_agent.UserAgent{},
cacheMap1: map[string]UserAgentParserResult{},
cacheMap2: map[string]UserAgentParserResult{},
parser: &useragent.UserAgent{},
cacheMap1: map[uint64]UserAgentParserResult{},
cacheMap2: map[uint64]UserAgentParserResult{},
cacheCursor: 0,
}
@@ -53,14 +54,16 @@ func (this *UserAgentParser) Parse(userAgent string) (result UserAgentParserResu
return
}
var userAgentKey = fnv.HashString(userAgent)
this.locker.RLock()
cacheResult, ok := this.cacheMap1[userAgent]
cacheResult, ok := this.cacheMap1[userAgentKey]
if ok {
this.locker.RUnlock()
return cacheResult
}
cacheResult, ok = this.cacheMap2[userAgent]
cacheResult, ok = this.cacheMap2[userAgentKey]
if ok {
this.locker.RUnlock()
return cacheResult
@@ -68,6 +71,8 @@ func (this *UserAgentParser) Parse(userAgent string) (result UserAgentParserResu
this.locker.RUnlock()
this.locker.Lock()
defer this.locker.Unlock()
this.parser.Parse(userAgent)
result.OS = this.parser.OSInfo()
result.BrowserName, result.BrowserVersion = this.parser.Browser()
@@ -83,19 +88,18 @@ func (this *UserAgentParser) Parse(userAgent string) (result UserAgentParserResu
}
if this.cacheCursor == 0 {
this.cacheMap1[userAgent] = result
this.cacheMap1[userAgentKey] = result
if len(this.cacheMap1) >= this.maxCacheItems {
this.cacheCursor = 1
this.cacheMap2 = map[string]UserAgentParserResult{}
this.cacheMap2 = map[uint64]UserAgentParserResult{}
}
} else {
this.cacheMap2[userAgent] = result
this.cacheMap2[userAgentKey] = result
if len(this.cacheMap2) >= this.maxCacheItems {
this.cacheCursor = 0
this.cacheMap1 = map[string]UserAgentParserResult{}
this.cacheMap1 = map[uint64]UserAgentParserResult{}
}
}
this.locker.Unlock()
return
}

View File

@@ -2,10 +2,12 @@
package stats
import "github.com/mssola/user_agent"
import (
"github.com/mssola/useragent"
)
type UserAgentParserResult struct {
OS user_agent.OSInfo
OS useragent.OSInfo
BrowserName string
BrowserVersion string
IsMobile bool

View File

@@ -12,7 +12,7 @@ import (
func TestUserAgentParser_Parse(t *testing.T) {
var parser = NewUserAgentParser()
for i := 0; i < 4; i ++ {
for i := 0; i < 4; i++ {
t.Log(parser.Parse("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Test/1"))
t.Log(parser.Parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"))
}
@@ -47,7 +47,25 @@ func TestUserAgentParser_Memory(t *testing.T) {
func BenchmarkUserAgentParser_Parse(b *testing.B) {
var parser = NewUserAgentParser()
for i := 0; i < b.N; i++ {
parser.Parse("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Test/" + types.String(rands.Int(0, 40000)))
parser.Parse("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Test/" + types.String(rands.Int(0, 1_000_000)))
}
b.Log(len(parser.cacheMap1), len(parser.cacheMap2))
}
func BenchmarkUserAgentParser_Parse2(b *testing.B) {
var parser = NewUserAgentParser()
for i := 0; i < b.N; i++ {
parser.Parse("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Test/" + types.String(rands.Int(0, 100_000)))
}
b.Log(len(parser.cacheMap1), len(parser.cacheMap2))
}
func BenchmarkUserAgentParser_Parse3(b *testing.B) {
var parser = NewUserAgentParser()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
parser.Parse("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Test/" + types.String(rands.Int(0, 100_000)))
}
})
b.Log(len(parser.cacheMap1), len(parser.cacheMap2))
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
)
var SharedCache = NewCache()
var SharedCache = NewBigCache()
// Cache TTL缓存
// 最大的缓存时间为30 * 86400
@@ -14,8 +14,6 @@ var SharedCache = NewCache()
// Piece1 | Piece2 | Piece3 | ...
// [ Item1, Item2, ... ] | ...
//
// KeyMap列表数据结构
// { timestamp1 => [key1, key2, ...] }, ...
type Cache struct {
isDestroyed bool
pieces []*Piece
@@ -25,6 +23,14 @@ type Cache struct {
gcPieceIndex int
}
func NewBigCache() *Cache {
var delta = utils.SystemMemoryGB() / 2
if delta <= 0 {
delta = 1
}
return NewCache(NewMaxItemsOption(delta * 1_000_000))
}
func NewCache(opt ...OptionInterface) *Cache {
var countPieces = 256
var maxItems = 1_000_000
@@ -34,7 +40,7 @@ func NewCache(opt ...OptionInterface) *Cache {
// 我们限制内存过小的服务能够使用的数量
maxItems = 500_000
} else {
var delta = totalMemory / 8
var delta = totalMemory / 4
if delta > 0 {
maxItems *= delta
}
@@ -138,12 +144,20 @@ func (this *Cache) Count() (count int) {
}
func (this *Cache) GC() {
this.pieces[this.gcPieceIndex].GC()
var newIndex = this.gcPieceIndex + 1
if newIndex >= int(this.countPieces) {
newIndex = 0
var index = this.gcPieceIndex
const maxPiecesPerGC = 4
for i := index; i < index+maxPiecesPerGC; i++ {
if i >= int(this.countPieces) {
break
}
this.pieces[i].GC()
}
this.gcPieceIndex = newIndex
index += maxPiecesPerGC
if index >= int(this.countPieces) {
index = 0
}
this.gcPieceIndex = index
}
func (this *Cache) Clean() {

View File

@@ -16,8 +16,8 @@ import (
func TestNewCache(t *testing.T) {
var cache = NewCache()
cache.Write("a", 1, time.Now().Unix()+3600)
cache.Write("b", 2, time.Now().Unix()+3601)
cache.Write("a", 1, time.Now().Unix()+3602)
cache.Write("b", 2, time.Now().Unix()+1)
cache.Write("c", 1, time.Now().Unix()+3602)
cache.Write("d", 1, time.Now().Unix()+1)
for _, piece := range cache.pieces {
@@ -28,8 +28,14 @@ func TestNewCache(t *testing.T) {
}
}
t.Log("a:", cache.Read("a"))
time.Sleep(2 * time.Second)
t.Log("d:", cache.Read("d")) // should be nil
time.Sleep(5 * time.Second)
for i := 0; i < len(cache.pieces); i++ {
cache.GC()
}
t.Log("b:", cache.Read("b"))
t.Log("d:", cache.Read("d"))
t.Log("left:", cache.Count(), "items")
}

View File

@@ -0,0 +1,72 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package connutils
import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"net"
"sync"
"time"
)
// 记录不需要带宽统计的连接
// 比如本地的清理和预热
var noStatAddrMap = map[string]zero.Zero{} // addr => Zero
var noStatLocker = &sync.RWMutex{}
// IsNoStatConn 检查是否为不统计连接
func IsNoStatConn(addr string) bool {
noStatLocker.RLock()
_, ok := noStatAddrMap[addr]
noStatLocker.RUnlock()
return ok
}
type NoStatConn struct {
rawConn net.Conn
}
func NewNoStat(rawConn net.Conn) net.Conn {
noStatLocker.Lock()
noStatAddrMap[rawConn.LocalAddr().String()] = zero.New()
noStatLocker.Unlock()
return &NoStatConn{rawConn: rawConn}
}
func (this *NoStatConn) Read(b []byte) (n int, err error) {
return this.rawConn.Read(b)
}
func (this *NoStatConn) Write(b []byte) (n int, err error) {
return this.rawConn.Write(b)
}
func (this *NoStatConn) Close() error {
err := this.rawConn.Close()
noStatLocker.Lock()
delete(noStatAddrMap, this.rawConn.LocalAddr().String())
noStatLocker.Unlock()
return err
}
func (this *NoStatConn) LocalAddr() net.Addr {
return this.rawConn.LocalAddr()
}
func (this *NoStatConn) RemoteAddr() net.Addr {
return this.rawConn.RemoteAddr()
}
func (this *NoStatConn) SetDeadline(t time.Time) error {
return this.rawConn.SetDeadline(t)
}
func (this *NoStatConn) SetReadDeadline(t time.Time) error {
return this.rawConn.SetReadDeadline(t)
}
func (this *NoStatConn) SetWriteDeadline(t time.Time) error {
return this.rawConn.SetWriteDeadline(t)
}

View File

@@ -17,6 +17,8 @@ func init() {
_ = SystemMemoryGB()
}
// SystemMemoryGB 系统内存GB数量
// 必须保证不小于1
func SystemMemoryGB() int {
if systemTotalMemory > 0 {
return systemTotalMemory
@@ -24,10 +26,10 @@ func SystemMemoryGB() int {
stat, err := mem.VirtualMemory()
if err != nil {
return 0
return 1
}
systemTotalMemory = int(stat.Total / 1024 / 1024 / 1024)
systemTotalMemory = int(stat.Total / (1<<30))
if systemTotalMemory <= 0 {
systemTotalMemory = 1
}

View File

@@ -0,0 +1,15 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package testutils
import "os"
// IsSingleTesting 判断当前测试环境是否为单个函数测试
func IsSingleTesting() bool {
for _, arg := range os.Args {
if arg == "-test.run" {
return true
}
}
return false
}

View File

@@ -82,8 +82,10 @@ func (this *BlockAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, reque
// output response
if this.StatusCode > 0 {
request.ProcessResponseHeaders(writer.Header(), this.StatusCode)
writer.WriteHeader(this.StatusCode)
} else {
request.ProcessResponseHeaders(writer.Header(), http.StatusForbidden)
writer.WriteHeader(http.StatusForbidden)
}
if len(this.URL) > 0 {

View File

@@ -134,6 +134,7 @@ func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req
// 占用一次失败次数
CaptchaIncreaseFails(req, this, waf.Id, group.Id, set.Id, CaptchaPageCodeInit)
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, req.WAFRaw(), CaptchaPath+"?info="+url.QueryEscape(info), http.StatusTemporaryRedirect)
return false, false

View File

@@ -67,6 +67,7 @@ func (this *Get302Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, requ
return true, false
}
request.ProcessResponseHeaders(writer.Header(), http.StatusFound)
http.Redirect(writer, request.WAFRaw(), Get302Path+"?info="+url.QueryEscape(info), http.StatusFound)
flusher, ok := writer.(http.Flusher)

View File

@@ -75,14 +75,15 @@ func (this *JSCookieAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, re
}
}
req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.Header().Set("Cache-Control", "no-cache")
var timestamp = types.String(time.Now().Unix())
var cookieValue = timestamp + "@" + types.String(set.Id) + "@" + fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId)))
_, _ = writer.Write([]byte(`<!DOCTYPE html>
var respHTML = `<!DOCTYPE html>
<html>
<head>
<title></title>
@@ -94,7 +95,10 @@ window.location.reload();
</head>
<body>
</body>
</html>`))
</html>`
writer.Header().Set("Content-Length", types.String(len(respHTML)))
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(respHTML))
// 记录失败次数
this.increaseFails(req, waf.Id, group.Id, set.Id)

View File

@@ -15,6 +15,9 @@ type PageAction struct {
}
func (this *PageAction) Init(waf *WAF) error {
if this.Status <= 0 {
this.Status = http.StatusForbidden
}
return nil
}
@@ -33,6 +36,7 @@ func (this *PageAction) WillChange() bool {
// Perform the action
func (this *PageAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (continueRequest bool, goNextSet bool) {
request.ProcessResponseHeaders(writer.Header(), this.Status)
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(this.Status)
_, _ = writer.Write([]byte(request.Format(this.Body)))

View File

@@ -92,6 +92,7 @@ func (this *Post307Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req
Value: info,
})
request.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, request.WAFRaw(), request.WAFRaw().URL.String(), http.StatusTemporaryRedirect)
flusher, ok := writer.(http.Flusher)

View File

@@ -146,6 +146,7 @@ func (this *RecordIPAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, re
var expiresAt = time.Now().Unix() + int64(timeout)
if this.Type == "black" {
request.ProcessResponseHeaders(writer.Header(), http.StatusForbidden)
writer.WriteHeader(http.StatusForbidden)
request.WAFClose()

View File

@@ -0,0 +1,44 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type RedirectAction struct {
BaseAction
Status int `yaml:"status" json:"status"`
URL string `yaml:"url" json:"url"`
}
func (this *RedirectAction) Init(waf *WAF) error {
if this.Status <= 0 {
this.Status = http.StatusTemporaryRedirect
}
return nil
}
func (this *RedirectAction) Code() string {
return ActionRedirect
}
func (this *RedirectAction) IsAttack() bool {
return false
}
// WillChange determine if the action will change the request
func (this *RedirectAction) WillChange() bool {
return true
}
// Perform the action
func (this *RedirectAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (continueRequest bool, goNextSet bool) {
request.ProcessResponseHeaders(writer.Header(), this.Status)
writer.Header().Set("Location", this.URL)
writer.WriteHeader(this.Status)
return false, false
}

View File

@@ -15,6 +15,7 @@ const (
ActionRecordIP ActionString = "record_ip" // 记录IP
ActionTag ActionString = "tag" // 标签
ActionPage ActionString = "page" // 显示网页
ActionRedirect ActionString = "redirect" // 跳转
ActionAllow ActionString = "allow" // allow
ActionGoGroup ActionString = "go_group" // go to next rule group
ActionGoSet ActionString = "go_set" // go to next rule set
@@ -87,6 +88,12 @@ var AllActions = []*ActionDefinition{
Instance: new(PageAction),
Type: reflect.TypeOf(new(PageAction)).Elem(),
},
{
Name: "跳转",
Code: ActionRedirect,
Instance: new(RedirectAction),
Type: reflect.TypeOf(new(RedirectAction)).Elem(),
},
{
Name: "跳到下一个规则分组",
Code: ActionGoGroup,

View File

@@ -26,18 +26,22 @@ func NewCaptchaValidator() *CaptchaValidator {
func (this *CaptchaValidator) Run(req requests.Request, writer http.ResponseWriter) {
var info = req.WAFRaw().URL.Query().Get("info")
if len(info) == 0 {
req.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("invalid request"))
return
}
m, err := utils.SimpleDecryptMap(info)
if err != nil {
req.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("invalid request"))
return
}
var timestamp = m.GetInt64("timestamp")
if timestamp < time.Now().Unix()-600 { // 10分钟之后信息过期
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, req.WAFRaw(), m.GetString("url"), http.StatusTemporaryRedirect)
return
}
@@ -50,16 +54,19 @@ func (this *CaptchaValidator) Run(req requests.Request, writer http.ResponseWrit
var waf = SharedWAFManager.FindWAF(policyId)
if waf == nil {
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusTemporaryRedirect)
return
}
var actionConfig = waf.FindAction(actionId)
if actionConfig == nil {
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusTemporaryRedirect)
return
}
captchaActionConfig, ok := actionConfig.(*CaptchaAction)
if !ok {
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusTemporaryRedirect)
return
}
@@ -183,8 +190,7 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Req
}
}
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = writer.Write([]byte(`<!DOCTYPE html>
var msgHTML = `<!DOCTYPE html>
<html>
<head>
<title>` + msgTitle + `</title>
@@ -206,7 +212,13 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Req
</head>
<body>` + body + `
</body>
</html>`))
</html>`
req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.Header().Set("Content-Length", types.String(len(msgHTML)))
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(msgHTML))
}
func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, originURL string, req requests.Request, writer http.ResponseWriter) (allow bool) {
@@ -226,6 +238,7 @@ func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int
// 加入到白名单
SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId, "")
req.ProcessResponseHeaders(writer.Header(), http.StatusSeeOther)
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusSeeOther)
return false
@@ -235,6 +248,7 @@ func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int
return false
}
req.ProcessResponseHeaders(writer.Header(), http.StatusSeeOther)
http.Redirect(writer, req.WAFRaw(), req.WAFRaw().URL.String(), http.StatusSeeOther)
}
}

View File

@@ -18,7 +18,14 @@ type RequestRefererBlockCheckpoint struct {
// RequestValue 计算checkpoint值
// 选项allowEmpty, allowSameDomain, allowDomains
func (this *RequestRefererBlockCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value interface{}, hasRequestBody bool, sysErr error, userErr error) {
var checkOrigin = options.GetBool("checkOrigin")
var referer = req.WAFRaw().Referer()
if len(referer) == 0 && checkOrigin {
var origin = req.WAFRaw().Header.Get("Origin")
if len(origin) > 0 && origin != "null" {
referer = "https://" + origin // 因为Origin都只有域名部分所以为了下面的URL 分析需要加上https://
}
}
if len(referer) == 0 {
if options.GetBool("allowEmpty") {

View File

@@ -22,18 +22,22 @@ func NewGet302Validator() *Get302Validator {
func (this *Get302Validator) Run(request requests.Request, writer http.ResponseWriter) {
var info = request.WAFRaw().URL.Query().Get("info")
if len(info) == 0 {
request.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("invalid request"))
return
}
m, err := utils.SimpleDecryptMap(info)
if err != nil {
request.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("invalid request"))
return
}
var timestamp = m.GetInt64("timestamp")
if time.Now().Unix()-timestamp > 5 { // 超过5秒认为失效
request.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("invalid request"))
return
@@ -49,5 +53,7 @@ func (this *Get302Validator) Run(request requests.Request, writer http.ResponseW
// 返回原始URL
var url = m.GetString("url")
request.ProcessResponseHeaders(writer.Header(), http.StatusFound)
http.Redirect(writer, request.WAFRaw(), url, http.StatusFound)
}

View File

@@ -38,6 +38,9 @@ type Request interface {
// Format 格式化变量
Format(string) string
// ProcessResponseHeaders 处理响应Header
ProcessResponseHeaders(headers http.Header, status int)
// DisableAccessLog 在当前请求中不使用访问日志
DisableAccessLog()
}