Compare commits

...
39 Commits
Author SHA1 Message Date
wweir 62176ffb65 feat: support fake site serve directory 2025-04-29 16:18:56 +08:00
wweir 5fe202314d feat: change log package to log/slog 2025-04-29 14:24:35 +08:00
wweir fc7183d70e feat: support force setting upstream dns server 2025-03-18 18:06:33 +08:00
wweir 4a9197a4ec fix: upstream all failed case 2024-11-03 00:40:23 +08:00
wweir 9eaf27fdcc fix(dns): serve interface parser 2024-10-12 10:26:29 +08:00
wweir ea6c2a29ad feat: reduce dependency on host DNS configuration 2024-10-12 10:10:52 +08:00
wweir 77c4154c7c fix: http_proxy 2024-10-12 07:20:39 +08:00
wweir eadb2f0bdf fix: sync example config with codes 2024-09-23 19:09:22 +08:00
wweir 19b61945ad fix: ipv6 support 2024-09-10 14:11:12 +08:00
wweir 3d6ee5e376 feat: support http(s)_proxy 2024-09-10 07:40:08 +08:00
wweir c0f704a5ad feat: add dns.serve_6 flag 2024-05-25 23:06:17 +08:00
wweir ac09663bc4 feat: multi ips, change default config 2024-05-14 14:06:46 +08:00
wweir 45c9eea6e6 feat: support listen on multi ip 2024-05-14 13:23:25 +08:00
wweir 0c83462b91 chore: update dependencies 2024-05-08 13:36:21 +08:00
wweir 15c02b55d8 feat: optimize for network changed case 2023-04-24 10:44:37 +08:00
wweir aca4636433 Merge pull request #27 from sower-proxy/dependabot/go_modules/golang.org/x/net-0.7.0
Bump golang.org/x/net from 0.0.0-20220531201128-c960675eff93 to 0.7.0
2023-03-08 19:06:59 +08:00
dependabot[bot] d0d635f8b0 Bump golang.org/x/net from 0.0.0-20220531201128-c960675eff93 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220531201128-c960675eff93 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-08 11:06:47 +00:00
wweir bde3b1bfa4 Merge pull request #26 from sower-proxy/dependabot/go_modules/golang.org/x/text-0.3.8
Bump golang.org/x/text from 0.3.7 to 0.3.8
2023-03-08 19:06:10 +08:00
dependabot[bot] 5dfefdf117 Bump golang.org/x/text from 0.3.7 to 0.3.8
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-23 08:47:32 +00:00
wweir 3f63038631 fix: file based rules parser 2022-07-15 15:15:02 +08:00
wweir 8d18bd6c5b feat: support do not config block rules 2022-07-14 19:55:20 +08:00
wweir 98a5f5702c feat: update dependency, avoid github warning 2022-06-03 08:23:14 +08:00
wweir 2948ffb7db feat: add yaml example config 2022-06-03 08:18:13 +08:00
wweir b85c3a63d7 Merge pull request #24 from wangtz0607/master
Update README.md
2022-06-03 07:01:29 +08:00
wtz 47e8076c06 Update README.md 2022-05-29 21:00:53 +08:00
wtz 10dc20e9e8 Update README.md 2022-05-29 20:56:26 +08:00
wweir 19dcf8e0d7 fix: dns proxy hang case 2022-05-29 12:43:48 +08:00
wweir 795bf73fb2 fix: DNS upstream connection close 2022-05-19 15:29:35 +08:00
wweir 3412643b6e feat: add restart policy 2022-05-16 07:28:29 +08:00
wweir 6fc97a3c9f feat: optimize code with 1.18 generic 2022-05-01 22:53:51 +08:00
wweir 78d36b0c19 feat: change default config for safety 2022-04-30 16:13:53 +08:00
wweir 5ebd33b0d7 feat: migrate to golang 1.18 2022-04-30 14:33:54 +08:00
wweir b1f152cc0c fix: dns upstream lookback case 2022-04-05 23:42:33 +08:00
wweir c0b77da05a fix(#22): ** preifx match none 2022-02-21 08:38:13 +08:00
wweir bad7ae702a chore: separate packages 2021-12-06 14:44:24 +08:00
wweir 05a1a3249a chore: update dependency 2021-12-03 11:01:45 +08:00
wweir 0f029f8c15 refactor: reduce memory usage 2021-11-15 14:21:23 +08:00
wweir 8c9a0bc1dd refactor: reduce memory usage 2021-11-15 11:33:15 +08:00
wweir 465ff316fd refactor: refactor suffix tree to reduce memory usage 2021-11-15 09:18:02 +08:00
31 changed files with 958 additions and 840 deletions
+21 -2
View File
@@ -1,3 +1,4 @@
# 远程、上游连接方式
remote {
type = "sower"
addr = "proxy.com"
@@ -7,19 +8,33 @@ remote {
}
dns {
disable = false
# 监听该 IP 的 UDP(53)、TCP(80、443) 端口,DNS 会将请求导向这个地址
serve = "127.0.0.1"
# serve6 = "<ipv6>"
# 监听的网络接口
# serve_iface = "lo"
# 必填,优先使用 DHCP 中的 DNS 服务器,如果没有则使用该地址
fallback = "223.5.5.5"
}
socks_5 {
addr = ":1080"
disable = false
# 不写 IP 则监听所有 IP
addr = "127.0.0.1:1080"
}
# 路由规则,各规则优先级:
# 禁止访问规则 > 直接访问规则 > 代理访问规则 > GEOIP 规则
# file 字段可填 URL 或本地文件路径,URL 将通过代理请求
# 禁止访问规则。被命中的请求,将被禁止访问。可用于广告过滤之类的目的
router "block" {
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/adlist/adlist.txt"
rules = []
}
# 直接访问规则。被命中的请求,将被直接访问,不再通过代理
router "direct" {
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chinalist/chinalist.txt"
rules = [
@@ -35,6 +50,7 @@ router "direct" {
]
}
# 代理访问规则。被命中的请求,将通过代理进行转发
router "proxy" {
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/gfwlist/gfwlist.txt"
rules = [
@@ -65,8 +81,11 @@ router "proxy" {
]
}
# GEOIP 规则。大陆境内的 IP,将直接访问。
# 非大陆境内的 IP,将通过另一套检测逻辑,确定是否需要进行代理。
# 为避免误伤一些非标页面端口(除 80/443 外),该规则只在 Socks5 模式下生效
router "country" {
mmdb = "Country.mmdb" # https://github.com/alecthw/mmdb_china_ip_list
// mmdb = "Country.mmdb" # https://github.com/alecthw/mmdb_china_ip_list
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chnroute/chnroute.txt"
rules = [
"127.0.0.0/8",
+1 -1
View File
@@ -15,6 +15,6 @@
<key>RunAtLoad</key>
<true/>
<key>WorkingDirectory</key>
<string>/usr/local/etc</string>
<string>/usr/local/etc/sower</string>
</dict>
</plist>
+2
View File
@@ -6,6 +6,8 @@ After=network.target
Type=simple
WorkingDirectory=/etc/sower
ExecStart=/usr/local/bin/sower -f /etc/sower/sower.hcl
Restart=on-failure
RestartSec=1min
[Install]
WantedBy=multi-user.target
+93
View File
@@ -0,0 +1,93 @@
[remote] # 远程、上游连接方式
type = "sower"
addr = "proxy.com"
password = "I_am_Passw0rd"
# type: "socks5"
# addr: "127.0.0.1:7890"
[dns]
disable = false
# 监听该 IP 的 UDP(53)、TCP(80、443) 端口,DNS 会将请求导向这个地址
serve = "127.0.0.1"
# serve6 = <ipv6>
# 监听的网络接口
# serve_iface = "lo"
# 必填,优先使用 DHCP 中的 DNS 服务器,如果没有则使用该地址
fallback = "223.5.5.5"
[socks_5]
disable = false
# 不写 IP 则监听所有 IP
addr = "127.0.0.1:1080"
# 路由规则,各规则优先级:
# 禁止访问规则 > 直接访问规则 > 代理访问规则 > GEOIP 规则
# file 字段可填 URL 或本地文件路径,URL 将通过代理请求
# 禁止访问规则。被命中的请求,将被禁止访问。可用于广告过滤之类的目的
[router.block]
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/adlist/adlist.txt"
rules = []
# 直接访问规则。被命中的请求,将被直接访问,不再通过代理
[router.direct]
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chinalist/chinalist.txt"
rules = [
"imap.*.*",
"imap.*.*.*",
"smtp.*.*",
"smtp.*.*.*",
"pop.*.*",
"pop.*.*.*",
"pop3.*.*",
"pop3.*.*.*",
"**.cn",
]
# 代理访问规则。被命中的请求,将通过代理进行转发
[router.proxy]
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/gfwlist/gfwlist.txt"
rules = [
"**.google.*",
"**.goo.gl",
"**.googleusercontent.com",
"**.googleapis.com",
"*.googlesource.com",
"**.youtube.com",
"**.ytimg.com",
"**.ggpht.com",
"**.googlevideo.com",
"**.facebook.com",
"**.fbcdn.net",
"**.twitter.com",
"**.twimg.com",
"**.blogspot.com",
"**.appspot.com",
"**.wikipedia.org",
"**.wikimedia.org",
"*.cloudfront.net",
"**.amazon.com",
"**.amazonaws.com",
"*.githubusercontent.com",
"*.githubassets.com",
"*.github.*",
"lookup-api.apple.com",
]
# GEOIP 规则。大陆境内的 IP,将直接访问。
# 非大陆境内的 IP,将通过另一套检测逻辑,确定是否需要进行代理。
# 为避免误伤一些非标页面端口(除 80/443 外),该规则只在 Socks5 模式下生效
[router.country]
mmdb = "Country.mmdb" # https://github.com/alecthw/mmdb_china_ip_list
file = "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chnroute/chnroute.txt"
rules = [
"127.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"10.0.0.0/8",
"17.0.0.0/8",
"100.64.0.0/10",
"224.0.0.0/4",
"fe80::/10",
]
+89
View File
@@ -0,0 +1,89 @@
remote: # 远程、上游连接方式
type: "sower"
addr: "proxy.com"
password: "I_am_Passw0rd"
# type: "socks5"
# addr: "127.0.0.1:7890"
dns:
disable: false
# 监听该 IP 的 UDP(53)、TCP(80、443) 端口,DNS 会将请求导向这个地址
serve: "127.0.0.1"
# serve6: <ipv6>
# 监听的网络接口
# serve_iface: "lo"
# 必填,优先使用 DHCP 中的 DNS 服务器,如果没有则使用该地址
fallback: "223.5.5.5"
socks_5:
disable: false
# 不写 IP 则监听所有 IP
addr: "127.0.0.1:1080"
# 路由规则,各规则优先级:
# 禁止访问规则 > 直接访问规则 > 代理访问规则 > GEOIP 规则
# file 字段可填 URL 或本地文件路径,URL 将通过代理请求
router:
# 禁止访问规则。被命中的请求,将被禁止访问。可用于广告过滤之类的目的
block:
file: "https://raw.githubusercontent.com/pexcn/daily/gh-pages/adlist/adlist.txt"
rules: []
# 直接访问规则。被命中的请求,将被直接访问,不再通过代理
direct:
file: "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chinalist/chinalist.txt"
rules:
- "imap.*.*"
- "imap.*.*.*"
- "smtp.*.*"
- "smtp.*.*.*"
- "pop.*.*"
- "pop.*.*.*"
- "pop3.*.*"
- "pop3.*.*.*"
- "**.cn"
# 代理访问规则。被命中的请求,将通过代理进行转发
proxy:
file: "https://raw.githubusercontent.com/pexcn/daily/gh-pages/gfwlist/gfwlist.txt"
rules:
- "**.google.*"
- "**.goo.gl"
- "**.googleusercontent.com"
- "**.googleapis.com"
- "*.googlesource.com"
- "**.youtube.com"
- "**.ytimg.com"
- "**.ggpht.com"
- "**.googlevideo.com"
- "**.facebook.com"
- "**.fbcdn.net"
- "**.twitter.com"
- "**.twimg.com"
- "**.blogspot.com"
- "**.appspot.com"
- "**.wikipedia.org"
- "**.wikimedia.org"
- "*.cloudfront.net"
- "**.amazon.com"
- "**.amazonaws.com"
- "*.githubusercontent.com"
- "*.githubassets.com"
- "*.github.*"
- "lookup-api.apple.com"
# GEOIP 规则。大陆境内的 IP,将直接访问。
# 非大陆境内的 IP,将通过另一套检测逻辑,确定是否需要进行代理。
# 为避免误伤一些非标页面端口(除 80/443 外),该规则只在 Socks5 模式下生效
country:
mmdb: "Country.mmdb" # https://github.com/alecthw/mmdb_china_ip_list
file: "https://raw.githubusercontent.com/pexcn/daily/gh-pages/chnroute/chnroute.txt"
rules:
- "127.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
- "10.0.0.0/8"
- "17.0.0.0/8"
- "100.64.0.0/10"
- "224.0.0.0/4"
- "fe80::/10"
+2
View File
@@ -7,6 +7,8 @@ Type=simple
ExecStart=/usr/local/bin/sowerd
Environment="FAKE_SITE=127.0.0.1:8080"
Environment="PASSWORD=I_am_Passw0rd"
Restart=on-failure
RestartSec=1min
[Install]
WantedBy=multi-user.target
+9 -9
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/setup-go@v2
with:
go-version: ^1.17
go-version: ^1.18
- uses: actions/checkout@v2
- name: build matrix
@@ -18,32 +18,32 @@ jobs:
mv .github/* . || true
make build GO='GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-linux-amd64.tar.gz sower sowerd sower.hcl sower.service sowerd.service
tar czvf sower-linux-amd64.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=arm CGO_ENABLED=0 go'
tar czvf sower-linux-arm.tar.gz sower sowerd sower.hcl sower.service sowerd.service
tar czvf sower-linux-arm.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go'
tar czvf sower-linux-arm64.tar.gz sower sowerd sower.hcl sower.service sowerd.service
tar czvf sower-linux-arm64.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=mips CGO_ENABLED=0 go'
tar czvf sower-linux-mips.tar.gz sower sowerd sower.hcl sower.service sowerd.service
tar czvf sower-linux-mips.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.service sowerd.service
make clean
make build GO='GOOS=linux GOARCH=mipsle CGO_ENABLED=0 go'
tar czvf sower-linux-mipsle.tar.gz sower sowerd sower.hcl sower.service sowerd.service
tar czvf sower-linux-mipsle.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.service sowerd.service
make clean
make build GO='GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go'
tar czvf sower-darwin-amd64.tar.gz sower sowerd sower.hcl sower.plist
tar czvf sower-darwin-amd64.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.plist
make clean
make build GO='GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go'
tar czvf sower-darwin-arm64.tar.gz sower sowerd sower.hcl sower.plist
tar czvf sower-darwin-arm64.tar.gz sower sowerd sower.hcl sower.yaml sower.toml sower.plist
make clean
make build GO='GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go'
mv sower sower.exe
mv sowerd sowerd.exe
tar czvf sower-windows-amd64.tar.gz sower.exe sowerd.exe sower.hcl sower_run.vbs
tar czvf sower-windows-amd64.tar.gz sower.exe sowerd.exe sower.hcl sower.yaml sower.toml sower_run.vbs
make clean
- uses: ncipollo/release-action@v1
+1
View File
@@ -18,6 +18,7 @@
.vscode
.idea
**.env
/bin
**.mmdb
+15 -14
View File
@@ -8,31 +8,31 @@
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
The sower is a cross-platform intelligent transparent proxy tool. It provide both socks5 proxy and DNS-based proxy. All these kinds of proxy support intelligent router.
Sower is a cross-platform intelligent transparent proxy tool. It provides both socks5 proxy and DNS-based proxy. All these kinds of proxies support intelligent routing.
If you already have another proxy solution, you can use it's socks5(h) service as a parent proxy to enjoy the sower's intelligent router.
If you already have another proxy solution, you can use its socks5(h) service as a parent proxy to enjoy sower's intelligent routing.
## Installation
To enjoy the sower, you need to deploy sower on both server-side(sowerd) and client-side(sower).
To enjoy sower, you need to deploy sower on both server-side(sowerd) and client-side(sower).
## Sowerd
_If you wanna use sower as secondary proxy to provide intelligent router, you can skip sowerd._
_If you want to use sower as a secondary proxy to provide intelligent routing, you can skip sowerd._
At the server-side, the sowerd runs just like a web server proxy. It will occupy two ports `80` / `443`.
On the server-side, sowerd runs just like a web server proxy. It will bind two ports `80` / `443`.
You can use your own certificate or the certificate automatically applied for by the sowerd from [`Let's Encrypt`](https://letsencrypt.org/).
You can use your own certificate or the certificate automatically applied for by sowerd from [`Let's Encrypt`](https://letsencrypt.org/).
There are two ways to run the sowerd service:
1. run the shell command with root permission
1. Run the command with root permission
```shell
# sowerd -password XXX -fake_site 127.0.0.1:8080
```
2. install as a systemd service
2. Install as a systemd service
```service
[Unit]
@@ -51,20 +51,21 @@ There are two ways to run the sowerd service:
## Sower
A config file is required in sower client side. [Here](https://github.com/wweir/sower/wiki/sower.hcl) is an usable example in China.
A config file is required on the sower client side. [Here](https://github.com/wweir/sower/wiki/sower.hcl) is a usable example in China.
`Sower` will take 4 port by default with root permission. They are: `udp(53)` / `tcp(80)` / `tcp(443)` / `tcp(1080)`.
`Sower` will bind 4 ports by default with root permission. They are: `udp(53)` / `tcp(80)` / `tcp(443)` / `tcp(1080)`.
After do the next three step, you can enjoy the intelligent transparent proxy solution:
After doing the next three steps, you can enjoy the intelligent transparent proxy solution:
1. run the command line with root permission:
1. Run the command with root permission:
```shell
# sower -f sower.hcl
```
2. changing your DNS server to `127.0.0.1` and
3. setting your proxy to `socks5h://127.0.0.1:1080`.
2. Change your DNS server to `127.0.0.1`.
3. Set your proxy to `socks5h://127.0.0.1:1080`.
## Architecture
+174 -95
View File
@@ -2,11 +2,14 @@ package main
import (
"bufio"
"compress/gzip"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strconv"
"strings"
"time"
@@ -15,9 +18,11 @@ import (
"github.com/cristalhq/aconfig/aconfighcl"
"github.com/cristalhq/aconfig/aconfigtoml"
"github.com/cristalhq/aconfig/aconfigyaml"
"github.com/lmittmann/tint"
"github.com/miekg/dns"
"github.com/pkg/errors"
"github.com/wweir/deferlog/log"
"github.com/sower-proxy/deferlog/v2"
"github.com/wweir/sower/pkg/suffixtree"
"github.com/wweir/sower/router"
)
@@ -25,6 +30,8 @@ var (
version, date string
conf = struct {
Debug bool `default:"false" usage:"debug mode"`
Remote struct {
Type string `default:"sower" required:"true" usage:"option: sower/trojan/socks5"`
Addr string `required:"true" usage:"proxy address, eg: proxy.com/127.0.0.1:7890"`
@@ -32,13 +39,16 @@ var (
}
DNS struct {
Disable bool `default:"false" usage:"disable DNS proxy"`
Serve string `default:"127.0.0.1" required:"true" usage:"dns server ip"`
Fallback string `default:"223.5.5.5" usage:"fallback dns server"`
Disable bool `default:"false" usage:"disable DNS proxy"`
Serve string `usage:"dns server ip"`
Serve6 string `usage:"dns server ipv6, eg: ::1"`
ServeIface string `usage:"use the IP in the net interface, if serve ip not setted. eg: eth0"`
Upstream string `usage:"upstream dns server"`
Fallback string `default:"223.5.5.5" required:"true" usage:"fallback dns server"`
}
Socks5 struct {
Disable bool `default:"false" usage:"disable sock5 proxy"`
Addr string `default:":1080" usage:"socks5 listen address"`
Addr string `default:"127.0.0.1:1080" usage:"socks5 listen address"`
} `flag:"socks5"`
Router struct {
@@ -69,9 +79,24 @@ var (
)
func init() {
fi, _ := os.Stdout.Stat()
noColor := (fi.Mode() & os.ModeCharDevice) == 0
deferlog.SetDefault(slog.New(tint.NewHandler(os.Stdout,
&tint.Options{AddSource: true, NoColor: noColor})))
if err := aconfig.LoaderFor(&conf, aconfig.Config{
AllowUnknownFields: true,
FileFlag: "f",
FileFlag: "c",
Files: []string{
"sower.hcl",
"sower.toml",
"sower.yaml",
"sower.yml",
"/etc/sower/sower.hcl",
"/etc/sower/sower.toml",
"/etc/sower/sower.yaml",
"/etc/sower/sower.yml",
},
FileDecoders: map[string]aconfig.FileDecoder{
".yml": aconfigyaml.New(),
".yaml": aconfigyaml.New(),
@@ -79,91 +104,146 @@ func init() {
".hcl": aconfighcl.New(),
},
}).Load(); err != nil {
log.Fatal().Err(err).
Interface("config", conf).
Msg("Load config")
slog.Error("load config", "error", err, "config", conf)
os.Exit(1)
}
if conf.DNS.ServeIface != "" {
iface, err := net.InterfaceByName(conf.DNS.ServeIface)
if err != nil {
slog.Error("get interface", "error", err, "iface", conf.DNS.ServeIface)
os.Exit(1)
}
addrs, err := iface.Addrs()
if err != nil {
slog.Error("get interface addresses", "error", err, "iface", conf.DNS.ServeIface)
os.Exit(1)
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
slog.Error("parse interface IP", "error", err, "iface", conf.DNS.ServeIface, "ip", ip.String())
os.Exit(1)
}
if ip.To4() != nil { // ipv4
if conf.DNS.Serve == "" {
conf.DNS.Serve = ip.String()
}
} else if ip.IsGlobalUnicast() { // ipv6 must be global unicast
if conf.DNS.Serve6 == "" {
conf.DNS.Serve6 = ip.String()
}
}
}
}
if !conf.DNS.Disable && conf.DNS.Serve == "" {
slog.Error("dns serve ip and serve interface not set")
os.Exit(1)
}
conf.Router.Direct.Rules = append(conf.Router.Direct.Rules,
conf.Remote.Addr, "**.in-addr.arpa", "**.ip6.arpa")
log.Info().
Str("version", version).
Str("date", date).
Interface("config", conf).
Msg("Starting")
slog.Info("starting sower", "version", version, "date", date, "config", fmt.Sprint(conf))
}
func main() {
proxtDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password)
r := router.NewRouter(conf.DNS.Serve, conf.DNS.Fallback, conf.Router.Country.MMDB, proxtDial)
r.SetRules(conf.Router.Block.Rules, conf.Router.Direct.Rules, conf.Router.Proxy.Rules,
conf.Router.Country.Rules)
upstreamDNS := conf.DNS.Upstream
if upstreamDNS == "" {
upstreamDNS = conf.DNS.Fallback
}
proxyDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password, upstreamDNS)
r := router.NewRouter(conf.DNS.Serve, conf.DNS.Upstream, conf.DNS.Fallback, conf.Router.Country.MMDB, proxyDial)
r.BlockRule = suffixtree.NewNodeFromRules(conf.Router.Block.Rules...)
r.DirectRule = suffixtree.NewNodeFromRules(conf.Router.Direct.Rules...)
r.ProxyRule = suffixtree.NewNodeFromRules(conf.Router.Proxy.Rules...)
r.AddCountryCIDRs(conf.Router.Country.Rules...)
go func() {
if conf.DNS.Disable {
log.Info().Msg("DNS proxy disabled")
return
if conf.DNS.Disable {
slog.Info("DNS proxy disabled")
} else {
ips := make([]string, 0, 2)
if strings.TrimSpace(conf.DNS.Serve) != "" {
ips = append(ips, conf.DNS.Serve)
}
if strings.TrimSpace(conf.DNS.Serve6) != "" {
ips = append(ips, conf.DNS.Serve6)
}
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(conf.DNS.Serve, "80"))
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
go ServeHTTP(lnHTTP, r)
for _, ip := range ips {
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(ip, "80"))
if err != nil {
slog.Error("listen port 80", "error", err, "listen_on", net.JoinHostPort(ip, "80"))
os.Exit(1)
}
go ServeHTTP(lnHTTP, r)
lnHTTPS, err := net.Listen("tcp", net.JoinHostPort(conf.DNS.Serve, "443"))
if err != nil {
log.Fatal().Err(err).Msg("listen port")
}
go ServeHTTPS(lnHTTPS, r)
lnHTTPS, err := net.Listen("tcp", net.JoinHostPort(ip, "443"))
if err != nil {
slog.Error("listen port 443", "error", err, "listen_on", net.JoinHostPort(ip, "443"))
os.Exit(1)
}
go ServeHTTPS(lnHTTPS, r)
log.Info().
Str("listen_on", conf.DNS.Serve).
Msg("DNS proxy started")
if err := dns.ListenAndServe(net.JoinHostPort(conf.DNS.Serve, "53"), "udp", r); err != nil {
log.Fatal().Err(err).Msg("serve dns")
go func(ip string) {
err := dns.ListenAndServe(net.JoinHostPort(ip, "53"), "udp", r)
if err != nil {
slog.Error("serve dns", "error", err, "listen_on", ip)
os.Exit(1)
}
}(ip)
}
}()
}
go func() {
if conf.Socks5.Disable {
log.Info().Msg("SOCKS5 proxy disabled")
slog.Info("SOCKS5 proxy disabled")
return
}
ln, err := net.Listen("tcp", conf.Socks5.Addr)
if err != nil {
log.Fatal().Err(err).Msg("listen port")
slog.Error("listen port", "error", err)
os.Exit(1)
}
log.Info().Msgf("SOCKS5 proxy listening on %s", conf.Socks5.Addr)
slog.Info("SOCKS5 proxy listening", "addr", conf.Socks5.Addr)
go ServeSocks5(ln, r)
}()
start := time.Now()
conf.Router.Block.Rules = append(conf.Router.Block.Rules,
loadRules(proxtDial, conf.Router.Block.File, conf.Router.Block.FilePrefix)...)
conf.Router.Direct.Rules = append(conf.Router.Direct.Rules,
loadRules(proxtDial, conf.Router.Direct.File, conf.Router.Direct.FilePrefix)...)
conf.Router.Proxy.Rules = append(conf.Router.Proxy.Rules,
loadRules(proxtDial, conf.Router.Proxy.File, conf.Router.Proxy.FilePrefix)...)
conf.Router.Country.Rules = append(conf.Router.Country.Rules,
loadRules(proxtDial, conf.Router.Country.File, conf.Router.Country.FilePrefix)...)
r.SetRules(conf.Router.Block.Rules, conf.Router.Direct.Rules, conf.Router.Proxy.Rules,
conf.Router.Country.Rules)
loadRule(r.BlockRule, proxyDial, conf.Router.Block.File, conf.Router.Block.FilePrefix)
loadRule(r.DirectRule, proxyDial, conf.Router.Direct.File, conf.Router.Direct.FilePrefix)
loadRule(r.ProxyRule, proxyDial, conf.Router.Proxy.File, conf.Router.Proxy.FilePrefix)
for line := range fetchRuleFile(proxyDial, conf.Router.Country.File) {
r.AddCountryCIDRs(line)
}
log.Info().
Dur("spend", time.Since(start)).
Int("blockRule", len(conf.Router.Block.Rules)).
Int("directRule", len(conf.Router.Direct.Rules)).
Int("proxyRule", len(conf.Router.Proxy.Rules)).
Int("countryRule", len(conf.Router.Country.Rules)).
Msg("Loaded rules, proxy started")
slog.Info("loaded rules, proxy started", "took", time.Since(start),
"blockRule", r.BlockRule.Count, "directRule", r.DirectRule.Count, "proxyRule", r.ProxyRule.Count)
runtime.GC()
select {}
}
func loadRules(proxyDial router.ProxyDialFn, file, linePrefix string) []string {
func loadRule(rule *suffixtree.Node, proxyDial router.ProxyDialFn, file, linePrefix string) {
for line := range fetchRuleFile(proxyDial, file) {
rule.Add(linePrefix + line)
}
rule.GC()
}
func fetchRuleFile(proxyDial router.ProxyDialFn, file string) <-chan string {
if file == "" {
return make(chan string)
}
var loadFn func() (io.ReadCloser, error)
if _, err := url.Parse(file); err == nil {
if _, err := os.Stat(file); err == nil {
// load rule file from local file
loadFn = func() (io.ReadCloser, error) {
return os.Open(file)
}
} else {
// load rule file from remote by HTTP
client := &http.Client{
Transport: &http.Transport{
@@ -176,7 +256,9 @@ func loadRules(proxyDial router.ProxyDialFn, file, linePrefix string) []string {
}
loadFn = func() (io.ReadCloser, error) {
resp, err := client.Get(file)
req, _ := http.NewRequest(http.MethodGet, file, nil)
req.Header.Add("Accept-Encoding", "gzip")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
@@ -188,12 +270,6 @@ func loadRules(proxyDial router.ProxyDialFn, file, linePrefix string) []string {
return resp.Body, nil
}
} else {
// load rule file from local file
loadFn = func() (io.ReadCloser, error) {
return os.Open(file)
}
}
// load rule file, retry 10 times
@@ -208,33 +284,36 @@ func loadRules(proxyDial router.ProxyDialFn, file, linePrefix string) []string {
rc, err = loadFn()
}
if err != nil {
log.Fatal().Err(err).
Str("file", file).
Msg("load config file")
}
defer rc.Close()
// parse rule file into rule tree
var lines []string
br := bufio.NewReader(rc)
for {
line, _, err := br.ReadLine()
if err == io.EOF {
break
} else if err != nil {
log.Error().Err(err).
Str("file", file).
Msg("read line")
return nil
}
if strings.TrimSpace(string(line)) == "" {
continue
}
// use line content as suffix
lines = append(lines, linePrefix+string(line))
slog.Error("fetch rule file", "error", err, "file", file)
os.Exit(1)
}
return lines
ch := make(chan string, 100)
go func() {
defer rc.Close()
defer close(ch)
if gr, err := gzip.NewReader(rc); err == nil {
rc = gr
defer gr.Close()
}
br := bufio.NewReader(rc)
for {
line, _, err := br.ReadLine()
if err == io.EOF {
return
} else if err != nil {
slog.Error("read line", "error", err, "file", file)
return
}
if strings.TrimSpace(string(line)) == "" {
continue
}
ch <- string(line)
}
}()
return ch
}
+89 -48
View File
@@ -2,51 +2,66 @@ package main
import (
"bufio"
"context"
"crypto/tls"
"log/slog"
"net"
"net/http"
"os"
"time"
"github.com/pkg/errors"
"github.com/wweir/deferlog/log"
"github.com/wweir/sower/pkg/teeconn"
"github.com/sower-proxy/conns/relay"
"github.com/sower-proxy/conns/reread"
"github.com/wweir/sower/router"
"github.com/wweir/sower/transport"
"github.com/wweir/sower/transport/socks5"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
"github.com/wweir/sower/util"
)
func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn {
func GenProxyDial(proxyType, proxyHost, proxyPassword, dns string) router.ProxyDialFn {
var proxy transport.Transport
var dialFn func() (net.Conn, error)
dialer := &net.Dialer{
Resolver: &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{}
c, err := d.DialContext(ctx, "udp", net.JoinHostPort(dns, "53"))
if err != nil {
slog.Warn("dial fallback dns failed, use default dns setting", "error", err)
c, err = d.DialContext(ctx, network, address)
}
return c, err
},
},
}
switch conf.Remote.Type {
case "sower":
proxy = sower.New(conf.Remote.Password)
tlsCfg := &tls.Config{}
dialFn = func() (net.Conn, error) {
return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
return tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
}
case "trojan":
proxy = trojan.New(conf.Remote.Password)
tlsCfg := &tls.Config{}
dialFn = func() (net.Conn, error) {
return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
return tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
}
case "socks5":
proxy = socks5.New()
dialFn = func() (net.Conn, error) {
return net.Dial("tcp", proxyHost)
return dialer.Dial("tcp", proxyHost)
}
default:
log.Fatal().
Str("type", conf.Remote.Type).
Msg("unknown proxy type")
slog.Error("unknown proxy type", "type", conf.Remote.Type)
os.Exit(1)
}
return func(network, host string, port uint16) (net.Conn, error) {
@@ -71,53 +86,49 @@ func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn
func ServeHTTP(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
slog.Error("serve socks5", "error", err)
os.Exit(1)
}
go ServeHTTP(ln, r)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
reread := reread.New(conn)
defer reread.Close()
req, err := http.ReadRequest(bufio.NewReader(teeconn))
req, err := http.ReadRequest(bufio.NewReader(reread))
if err != nil {
log.Error().Err(err).Msg("read http request")
slog.Error("read http request", "error", err)
return
}
rc, err := r.ProxyDial("tcp", req.Host, 80)
if err != nil {
log.Error().Err(err).
Str("host", req.Host).
Interface("req", req.URL).
Msg("dial proxy")
slog.Error("dial proxy", "error", err, "host", req.Host, "req", req.URL)
return
}
defer rc.Close()
teeconn.Stop().Reread()
util.Relay(teeconn, rc)
log.Debug().
Str("host", req.Host).
Dur("spend", time.Since(start)).
Msg("serve http")
reread.Stop().Reread()
err = relay.Relay(reread, rc)
if err != nil {
slog.Debug("serve http", "error", err, "host", req.Host, "spend", time.Since(start))
}
}
func ServeHTTPS(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
slog.Error("serve socks5", "error", err)
os.Exit(1)
}
go ServeHTTPS(ln, r)
start := time.Now()
teeconn := teeconn.New(conn)
defer teeconn.Close()
reread := reread.New(conn)
defer reread.Close()
var domain string
tls.Server(teeconn, &tls.Config{
tls.Server(reread, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
domain = hello.ServerName
return nil, nil
@@ -126,37 +137,67 @@ func ServeHTTPS(ln net.Listener, r *router.Router) {
rc, err := r.ProxyDial("tcp", domain, 443)
if err != nil {
log.Error().Err(err).
Str("host", domain).
Msg("dial proxy")
slog.Error("dial proxy", "error", err, "host", domain)
return
}
defer rc.Close()
teeconn.Stop().Reread()
util.Relay(teeconn, rc)
log.Debug().
Str("host", domain).
Dur("spend", time.Since(start)).
Msg("serve http")
reread.Stop().Reread()
err = relay.Relay(reread, rc)
if err != nil {
slog.Debug("serve http", "error", err, "host", domain, "spend", time.Since(start))
}
}
func ServeSocks5(ln net.Listener, r *router.Router) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).
Msg("serve socks5")
slog.Error("serve socks5", "error", err)
os.Exit(1)
}
go ServeSocks5(ln, r)
defer conn.Close()
addr, err := socks5.New().Unwrap(conn)
if err != nil {
log.Warn().Err(err).
Msgf("parse socks5 target: %s", addr)
reread := reread.New(conn)
byte1 := make([]byte, 1)
if n, err := reread.Read(byte1); err != nil || n != 1 {
slog.Error("read first byte", "error", err)
return
}
reread.Reread()
if byte1[0] == 5 {
reread.Stop()
if addr, err := socks5.New().Unwrap(reread); err != nil {
slog.Error("read socks5 request", "error", err)
} else {
host, port := addr.(*socks5.AddrHead).Addr()
r.RouteHandle(reread, host, port)
}
return
}
host, port := addr.(*socks5.AddrHead).Addr()
r.RouteHandle(conn, host, port)
req, err := http.ReadRequest(bufio.NewReader(reread))
if err != nil {
slog.Error("read http request", "error", err)
return
}
host, port, err := router.ParseHostPort(req.Host, req.URL)
if err != nil {
reread.Stop().Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
return
}
if req.Method == http.MethodConnect {
// https
reread.Stop().Reset()
reread.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
} else {
// http
reread.Stop().Reread()
}
r.RouteHandle(reread, host, port)
}
+60 -52
View File
@@ -2,6 +2,8 @@ package main
import (
"crypto/tls"
"log"
"log/slog"
"net"
"net/http"
"os"
@@ -9,12 +11,12 @@ import (
"time"
"github.com/cristalhq/aconfig"
"github.com/wweir/deferlog"
"github.com/wweir/deferlog/log"
"github.com/wweir/sower/pkg/teeconn"
"github.com/lmittmann/tint"
"github.com/sower-proxy/conns/relay"
"github.com/sower-proxy/conns/reread"
"github.com/sower-proxy/deferlog/v2"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
"github.com/wweir/sower/util"
"golang.org/x/crypto/acme/autocert"
)
@@ -22,9 +24,9 @@ var (
version, date string
conf = struct {
ServeIP string `usage:"listen to port 80 443 of this IP, eg: 0.0.0.0"`
ServeIP string `required:"true" usage:"listen to port 80 443 of the IP"`
Password string `required:"true"`
FakeSite string `required:"true" default:"127.0.0.1:8080" usage:"fake site address"`
FakeSite string `default:"/var/www" usage:"fake site address or directoy. serving on 127.0.0.1:80 if directory"`
Cert struct {
Email string
@@ -35,21 +37,25 @@ var (
)
func init() {
fi, _ := os.Stdout.Stat()
noColor := (fi.Mode() & os.ModeCharDevice) == 0
deferlog.SetDefault(slog.New(tint.NewHandler(os.Stdout,
&tint.Options{AddSource: true, NoColor: noColor})))
err := aconfig.LoaderFor(&conf, aconfig.Config{}).Load()
log.InfoFatal(err).
Str("version", version).
Str("date", date).
Interface("config", conf).
Msg("Load config")
if err != nil {
slog.Error("load config", "error", err)
os.Exit(1)
}
slog.Info("load config", "version", version, "date", date, "config", conf)
}
func main() {
cacheDir, _ := os.UserCacheDir()
cacheDir = filepath.Join(cacheDir, "sower")
if err := os.MkdirAll(cacheDir, 0600); err != nil {
log.Fatal().Err(err).
Str("dir", cacheDir).
Msg("make cache dir")
if err := os.MkdirAll(cacheDir, 0o600); err != nil {
slog.Error("create cache dir", "error", err)
os.Exit(1)
}
certManager := autocert.Manager{
@@ -66,7 +72,8 @@ func main() {
if conf.Cert.Cert != "" || conf.Cert.Key != "" {
cert, err := tls.LoadX509KeyPair(conf.Cert.Cert, conf.Cert.Key)
if err != nil {
log.Fatal().Err(err).Msg("load certificate")
slog.Error("load cert", "error", err)
os.Exit(1)
}
tlsConf.GetCertificate = nil
@@ -75,69 +82,70 @@ func main() {
// Redirect 80 to 443
go func() {
err := http.ListenAndServe(net.JoinHostPort(conf.ServeIP, "80"),
certManager.HTTPHandler(http.HandlerFunc(redirectToHTTPS)))
log.Fatal().Err(err).
Str("IP", conf.ServeIP).
Msg("Listen HTTP service")
addr := net.JoinHostPort(conf.ServeIP, "80")
err := http.ListenAndServe(addr, certManager.HTTPHandler(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
target := "https://" + r.Host + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusPermanentRedirect)
})))
if err != nil {
slog.Error("listen http", "error", err)
os.Exit(1)
}
}()
ln, err := tls.Listen("tcp", net.JoinHostPort(conf.ServeIP, "443"), tlsConf)
log.InfoFatal(err).
Str("IP", conf.ServeIP).
Msg("Start listen HTTPS service")
go serve443(ln, conf.FakeSite, sower.New(conf.Password), trojan.New(conf.Password))
select {}
}
func redirectToHTTPS(w http.ResponseWriter, r *http.Request) {
r.URL.Scheme = "https"
if host, _, err := net.SplitHostPort(r.Host); err != nil {
r.URL.Host = r.Host
} else {
r.URL.Host = host
if err != nil {
slog.Error("listen https", "error", err)
os.Exit(1)
}
http.Redirect(w, r, r.URL.String(), 301)
if si, err := os.Stat(conf.FakeSite); err == nil && si.IsDir() {
go http.ListenAndServe("127.0.0.1:80", http.FileServer(http.Dir(conf.FakeSite)))
conf.FakeSite = "127.0.0.1:80"
}
if _, _, err := net.SplitHostPort(conf.FakeSite); err != nil {
log.Fatalln("fake site address must be a valid host:port")
}
serve443(ln, conf.FakeSite, sower.New(conf.Password), trojan.New(conf.Password))
}
func serve443(ln net.Listener, fakeSite string, sower *sower.Sower, trojan *trojan.Trojan) {
conn, err := ln.Accept()
if err != nil {
log.Fatal().Err(err).Msg("serve 443 port")
slog.Error("serve 443 port", "error", err)
os.Exit(1)
}
go serve443(ln, fakeSite, sower, trojan)
teeconn := teeconn.New(conn)
defer teeconn.Close()
reread := reread.New(conn)
defer reread.Close()
var addr net.Addr
var dur time.Duration
defer func() {
deferlog.DebugWarn(err).
Dur("spend", dur).
Msgf("relay conn to %s", addr)
deferlog.DebugWarn(err, "relay conn", "took", dur, "addr", addr)
}()
// 1. detect if it's a sower underlaying connection
teeconn.Reread()
if addr, err = sower.Unwrap(teeconn); err == nil {
teeconn.Stop()
reread.Reread()
if addr, err = sower.Unwrap(reread); err == nil {
reread.Stop()
dur, err = util.RelayTo(teeconn, addr.String())
dur, err = relay.RelayTo(reread, addr.String())
return
}
// 2. detect if it's a trojan underlaying connection
teeconn.Reread()
if addr, err = trojan.Unwrap(teeconn); err == nil {
teeconn.Stop()
reread.Reread()
if addr, err = trojan.Unwrap(reread); err == nil {
reread.Stop()
dur, err = util.RelayTo(teeconn, addr.String())
dur, err = relay.RelayTo(reread, addr.String())
return
}
// 3. fallback to fake site
teeconn.Stop().Reread()
dur, err = util.RelayTo(teeconn, fakeSite)
reread.Stop().Reread()
dur, err = relay.RelayTo(reread, fakeSite)
}
+23 -18
View File
@@ -1,29 +1,34 @@
module github.com/wweir/sower
go 1.17
go 1.24
require (
github.com/cristalhq/aconfig v0.16.8
github.com/cristalhq/aconfig/aconfighcl v0.16.1
github.com/cristalhq/aconfig/aconfigtoml v0.16.1
github.com/cristalhq/aconfig/aconfigyaml v0.16.1
github.com/cristalhq/aconfig v0.18.7
github.com/cristalhq/aconfig/aconfighcl v0.17.1
github.com/cristalhq/aconfig/aconfigtoml v0.17.1
github.com/cristalhq/aconfig/aconfigyaml v0.17.1
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771
github.com/libp2p/go-reuseport v0.1.0
github.com/miekg/dns v1.1.43
github.com/oschwald/geoip2-golang v1.5.0
github.com/libp2p/go-reuseport v0.4.0
github.com/lmittmann/tint v1.0.7
github.com/miekg/dns v1.1.65
github.com/oschwald/geoip2-golang v1.11.0
github.com/pkg/errors v0.9.1
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
github.com/wweir/deferlog v0.1.0
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa
github.com/sower-proxy/conns v0.1.1
github.com/sower-proxy/deferlog/v2 v2.0.1
github.com/sower-proxy/mem v0.0.3
golang.org/x/crypto v0.37.0
)
require (
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/BurntSushi/toml v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
github.com/rs/zerolog v1.23.0 // indirect
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 // indirect
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+49 -80
View File
@@ -1,92 +1,61 @@
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/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cristalhq/aconfig v0.16.1/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.16.8 h1:lg8i0XHgfhvsnjNM5q/ou6jIHDRXlbBybjRP9t2fWuw=
github.com/cristalhq/aconfig v0.16.8/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfighcl v0.16.1 h1:RiziovFKOwB6+H1LCD7DXxrFgoKDEJxxYLSsAOsj+vQ=
github.com/cristalhq/aconfig/aconfighcl v0.16.1/go.mod h1:YvZyVGtty/7yIdyOPPl68u7ENaUMq6ybZAph+n3g4II=
github.com/cristalhq/aconfig/aconfigtoml v0.16.1 h1:q9DJfJqdLvZj7vL3bOIR6QM0ySpA0i6X8CZ8FNob6nc=
github.com/cristalhq/aconfig/aconfigtoml v0.16.1/go.mod h1:VfRDnJBq09TZXiQdnnd4CBsNyj4s7AEDJlCxDSDt5DI=
github.com/cristalhq/aconfig/aconfigyaml v0.16.1 h1:ghmzWFolCuja6BzBvHkNAn2Qsewu6xqdAXQyWnXjvZw=
github.com/cristalhq/aconfig/aconfigyaml v0.16.1/go.mod h1:UgM0LkO4TKbC0s/oUP2p5POM6QiQwNvOFXostf6con0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.18.7 h1:ZvgaiSz7D3++TrXN9DrTSWA71eFuig0HhBY32nblLOk=
github.com/cristalhq/aconfig v0.18.7/go.mod h1:9ogrGEt9yU5V4pif/ThkVUfhj8JkdV+iDeahZGgfnDU=
github.com/cristalhq/aconfig/aconfighcl v0.17.1 h1:/hJvmmoP3akvjY8qARiXgN30m/WXeBvpQdQ8v4DE8W8=
github.com/cristalhq/aconfig/aconfighcl v0.17.1/go.mod h1:oOPqMmdUjVW6pKKO1zkOwQKHSUD0rlu8F5do+pylOQc=
github.com/cristalhq/aconfig/aconfigtoml v0.17.1 h1:TA3xH8mALD8YeULsr4v87cMbKGBma35lWeRga4Xn11Q=
github.com/cristalhq/aconfig/aconfigtoml v0.17.1/go.mod h1:xt4kCEjhgvHWO/oDGJHQHrW5CnvSEoBjJhRXVBdYbhQ=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk=
github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
github.com/libp2p/go-reuseport v0.1.0 h1:0ooKOx2iwyIkf339WCZ2HN3ujTDbkK0PjC7JVoP1AiM=
github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU=
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/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs=
github.com/wweir/deferlog v0.1.0 h1:jzVo2Toct+0Ei29P3BpjVJpNgCLyyMZYhi+zr7L0bLw=
github.com/wweir/deferlog v0.1.0/go.mod h1:cHSuGypQc2+yDurLohzOdJ2TpaQqGNm/6EhzskmBGZM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 h1:v3NKo+t/Kc3EASxaKZ82lwK6mCf4ZeObQBduYFZHo7c=
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 h1:T6tyxxvHMj2L1R2kZg0uNMpS8ZhB9lRa9XRGTCSA65w=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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.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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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/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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/sower-proxy/conns v0.1.1 h1:9VPVW+sFc/y/IvXbUQTbXNSPF3I+1IG5mbrQTt5bdTE=
github.com/sower-proxy/conns v0.1.1/go.mod h1:p3ZM/8MolzW4TN0LaiPJnwnhhif3y9sBi2vQEWHvwhg=
github.com/sower-proxy/deferlog/v2 v2.0.1 h1:HHU5C0LkgrgEPqudafdUqAUETAnnnu7GuNwvVvvt4/0=
github.com/sower-proxy/deferlog/v2 v2.0.1/go.mod h1:mdLrL/bxiTNhxeRLtRgKAecQddugI0iJ0ArC3yDwVSw=
github.com/sower-proxy/mem v0.0.3 h1:JK86etQ2QxRUXhs9JhoKYz5p3pFBSrI3UXVhwm44OzM=
github.com/sower-proxy/mem v0.0.3/go.mod h1:tM3P+gCAZMVBnOixGVAHS34si9Iwj2HU6M4IVEv4rK0=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+14 -9
View File
@@ -14,10 +14,10 @@ import (
var xid = make([]byte, 4)
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
func GetDNSServer() (string, error) {
func GetDNSServer() ([]string, error) {
iface, err := PickInternetInterface()
if err != nil {
return "", errors.Wrap(err, "pick interface")
return nil, errors.Wrap(err, "pick interface")
}
rand.Read(xid)
@@ -29,31 +29,36 @@ func GetDNSServer() (string, error) {
var conn net.PacketConn
if runtime.GOOS == "windows" {
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
return "", errors.Wrap(err, "listen dhcp")
return nil, errors.Wrap(err, "listen dhcp")
}
} else {
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
return "", errors.Wrap(err, "listen dhcp")
return nil, errors.Wrap(err, "listen dhcp")
}
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(3 * time.Second))
if _, err := conn.WriteTo([]byte(pack), broadcastAddr); err != nil {
return "", errors.Wrap(err, "write broadcast")
return nil, errors.Wrap(err, "write broadcast")
}
buf := make([]byte, 1500 /*MTU*/)
n, _, err := conn.ReadFrom(buf)
if err != nil {
return "", errors.Wrap(err, "read dhcp offer")
return nil, errors.Wrap(err, "read dhcp offer")
}
pack = dhcp4.Packet(buf[:n])
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
if len(dnsBytes) < 4 {
return "", errors.New("no DNS setting in upstream network device")
if len(dnsBytes) < 4 || len(dnsBytes)%4 != 0 {
return nil, errors.New("invalid DNS setting in upstream network device")
}
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
ips := []string{}
for i := 0; i < len(dnsBytes); i += 4 {
ips = append(ips, net.IP(dnsBytes[i:i+4]).String())
}
return ips, nil
}
-139
View File
@@ -1,139 +0,0 @@
package mem
import (
"errors"
"reflect"
"sync"
"time"
"github.com/ulule/deepcopier"
)
// Data define the type which can speed up by mem cache
type Data interface {
Fulfill(key string) (err error)
}
// Cache is the definition of cache, be careful of the memory usage
type Cache struct {
old *sync.Map
now *sync.Map
barrier *sync.Map
rotate <-chan time.Time
rwmutex *sync.RWMutex
}
// DefaultCache is default cache for surge
var DefaultCache = New(time.Minute)
// Remember is a surge, it provides a quite simple way to use cache
func Remember(dst Data, key string) error {
return DefaultCache.Remember(dst, key)
}
// Delete is a surge, it delete a specified data in DefaultCache
func Delete(dst Data, key string) {
DefaultCache.Delete(dst, key)
}
// New create a cache entity with a custom expiration time
func New(rotateInterval time.Duration) *Cache {
return &Cache{
old: &sync.Map{},
now: &sync.Map{},
barrier: &sync.Map{},
rotate: time.NewTicker(rotateInterval).C,
rwmutex: &sync.RWMutex{},
}
}
// Remember automatically save and retrieve data from a cache entity
func (c *Cache) Remember(dst Data, key string) error {
rv := reflect.ValueOf(dst)
if rv.Kind() != reflect.Ptr {
panic("invalid not pointor type: " + reflect.TypeOf(dst).Name())
} else if rv.IsNil() {
return errors.New("invalid nil pointor")
}
c.rwmutex.RLock()
defer c.rwmutex.RUnlock()
// rotate logic, rwlock just protect fields in Cache, but not field content.
// So that, write lock just take a very short time, and simple read lock is
// just an atomic action, do not care the performance
select {
case <-c.rotate:
c.old = c.now
c.now = &sync.Map{}
c.barrier = &sync.Map{}
default:
}
// First: load from cache
if val, ok := c.now.Load(key); ok {
return deepcopier.Copy(val).To(dst)
}
// Second: load from old cache, or waitting the sigle groutine getting data
ch := make(chan struct{})
if chVal, ok := c.barrier.LoadOrStore(key, ch); ok {
close(ch) // the ch is not used
if val, ok := c.old.Load(key); ok {
return deepcopier.Copy(val).To(dst)
}
// type chan: wait the sigle groutine getting data
// type error: already failed
if ch, ok = chVal.(chan struct{}); ok {
<-ch
if val, ok := c.now.Load(key); ok {
return deepcopier.Copy(val).To(dst)
}
}
val, _ := c.barrier.Load(key)
if err, ok := val.(error); ok {
return err
}
panic("new value lost, please report a bug")
}
// Third: getting data from CacheType, maybe from db
err := dst.Fulfill(key)
if err != nil {
c.barrier.Store(key, err)
return err
}
c.now.Store(key, dst)
close(ch) // broadcast, wakeup all waiting groutine
return nil
}
// Delete immediately specified the cached content to expire
func (c *Cache) Delete(dst Data, key string) {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
c.old.Delete(key)
c.now.Delete(key)
c.barrier.Store(key, errors.New(key+" is deleted"))
}
// Rotate force refresh cached data
func (c *Cache) Rotate(reset bool) {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
if reset {
c.old = &sync.Map{}
} else {
c.old = c.now
}
c.now = &sync.Map{}
c.barrier = &sync.Map{}
}
+143
View File
@@ -0,0 +1,143 @@
package suffixtree
import (
"strings"
)
type Node struct {
*node
sep string
Count uint64
}
type node struct {
secs []string
subNodes []*node
}
func NewNodeFromRules(rules ...string) *Node {
n := &Node{&node{}, ".", 0}
for i := range rules {
n.Add(rules[i])
}
n.GC()
return n
}
func (n *node) GC() {
if n == nil {
return
}
n.secs = GCSlice(n.secs)
n.subNodes = GCSlice(n.subNodes)
for i := range n.subNodes {
n.subNodes[i].GC()
}
}
func (n *Node) String() string {
return n.string("", " ")
}
func (n *node) string(prefix, indent string) (out string) {
if n == nil {
return
}
for key, val := range n.subNodes {
out += prefix + n.secs[key] + "\n" + val.string(prefix+indent, indent)
}
return
}
func (n *Node) trim(item string) string {
return strings.TrimSuffix(item, n.sep)
}
func (n *Node) Add(item string) {
n.Count++
n.add(strings.Split(n.trim(item), n.sep))
}
func (n *node) add(secs []string) {
length := len(secs)
switch length {
case 0:
case 1:
sec := secs[length-1]
switch sec {
case "", "*", "**":
n.secs = append([]string{sec}, n.secs...)
n.subNodes = append([]*node{nil}, n.subNodes...)
default:
n.secs = append(n.secs, sec)
n.subNodes = append(n.subNodes, nil)
}
default:
sec := secs[length-1]
if sec == "**" { // ** is only allowed in the last sec
sec = "*"
}
idx := n.index(sec)
if idx == -1 {
switch sec {
case "", "*", "**":
idx = 0
n.secs = append([]string{sec}, n.secs...)
n.subNodes = append([]*node{{}}, n.subNodes...)
default:
idx = len(n.secs)
n.secs = append(n.secs, sec)
n.subNodes = append(n.subNodes, &node{})
}
} else if n.subNodes[idx] == nil {
n.subNodes[idx] = &node{}
n.subNodes[idx].add([]string{""})
}
n.subNodes[idx].add(secs[:length-1])
}
}
func (n *Node) Match(item string) bool {
if n == nil {
return false
}
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
}
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
length := len(secs)
if length == 0 {
if n == nil {
return true
}
return n.index("") != -1 || n.index("**") != -1
}
if idx := n.index(secs[length-1]); idx >= 0 {
if n.subNodes[idx].matchSecs(secs[:length-1], false) {
return true
}
}
if idx := n.index("*"); idx >= 0 {
if n.subNodes[idx].matchSecs(secs[:length-1], true) {
return true
}
}
return n.index("**") >= 0
}
// index return the sec index in node, or -1 if not found
func (n *node) index(sec string) int {
if n == nil {
return -1
}
for s := range n.secs {
if n.secs[s] == sec {
return s
}
}
return -1
}
@@ -1,9 +1,9 @@
package util_test
package suffixtree_test
import (
"testing"
"github.com/wweir/sower/util"
"github.com/wweir/sower/pkg/suffixtree"
)
func TestNode_Match(t *testing.T) {
@@ -13,18 +13,18 @@ func TestNode_Match(t *testing.T) {
}
tests := []struct {
name string
node *util.Node
node *suffixtree.Node
tests []test
}{{
"simple",
util.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
suffixtree.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
[]test{
{"a.wweir.cc", true},
{"b.wweir.cc", true},
},
}, {
"parent",
util.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
suffixtree.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -32,7 +32,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz1",
util.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
suffixtree.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -41,7 +41,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz2",
util.NewNodeFromRules("a.*.cc", "c.wweir.*"),
suffixtree.NewNodeFromRules("a.*.cc", "c.wweir.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
@@ -50,7 +50,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz3",
util.NewNodeFromRules("*.*.cc", "iamp.*.*"),
suffixtree.NewNodeFromRules("*.*.cc", "iamp.*.*"),
[]test{
{"wweir.cc", false},
{"a.wweir.cc", true},
@@ -59,7 +59,7 @@ func TestNode_Match(t *testing.T) {
},
}, {
"fuzz4",
util.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
suffixtree.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
[]test{
{"wweir.cc", true},
{"a.wweir.cc", true},
@@ -68,6 +68,12 @@ func TestNode_Match(t *testing.T) {
{"b.fuzz.com", false},
{"www.wweir.com", true},
},
}, {
"github",
suffixtree.NewNodeFromRules("**.github.com"),
[]test{
{"github.com", true},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+6
View File
@@ -0,0 +1,6 @@
package suffixtree
func GCSlice[T any](arr []T) []T {
old := arr
return append(make([]T, 0, len(old)), old...)
}
-56
View File
@@ -1,56 +0,0 @@
package teeconn
import (
"io"
"net"
)
type Conn struct {
net.Conn
buf []byte
offset int
stop bool // read
err error
}
func New(c net.Conn) *Conn {
return &Conn{Conn: c}
}
func (t *Conn) Reread() {
t.offset = 0
}
func (t *Conn) Reset() {
t.buf = []byte{}
t.offset = 0
}
func (t *Conn) Stop() *Conn {
t.stop = true
return t
}
func (t *Conn) Read(b []byte) (n int, err error) {
length := len(t.buf) - t.offset
if length > 0 {
n = copy(b, t.buf[t.offset:])
t.offset += n
return n, t.err
}
n, t.err = t.Conn.Read(b)
if !t.stop {
t.buf = append(t.buf, b[:n]...)
t.offset += n
}
return n, t.err
}
func (t *Conn) Write(b []byte) (n int, err error) {
if t.stop {
return t.Conn.Write(b)
}
return 0, io.ErrShortWrite
}
+3 -10
View File
@@ -2,10 +2,9 @@ package router
import (
"context"
"log/slog"
"net"
"time"
"github.com/wweir/deferlog/log"
)
func (r *Router) localSite(domain string) bool {
@@ -17,10 +16,7 @@ func (r *Router) localSite(domain string) bool {
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", domain)
if err != nil || len(ips) == 0 {
log.Warn().Err(err).
Str("domain", domain).
Int("ips", len(ips)).
Msg("resolve domain")
slog.Warn("resolve domain", "error", err, "domain", domain, "ips", len(ips))
return false
}
@@ -38,10 +34,7 @@ func (r *Router) localSite(domain string) bool {
if r.country.Reader != nil {
city, err := r.country.City(ip)
if err != nil {
log.Warn().Err(err).
Str("domain", domain).
IPAddr("ip", ip).
Msg("mmdb search")
slog.Warn("mmdb search", "error", err, "domain", domain, "ip", ip)
return false
}
+52 -34
View File
@@ -1,11 +1,12 @@
package router
import (
"log/slog"
"net"
"time"
"sync/atomic"
"github.com/miekg/dns"
"github.com/wweir/deferlog/log"
"github.com/wweir/sower/pkg/dhcp"
)
func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
@@ -18,32 +19,35 @@ func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
domain := req.Question[0].Name
// 1. rule_based( block > direct > proxy )
switch {
case r.blockRule.Match(domain):
case r.BlockRule.Match(domain):
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeNameError))
return
case r.directRule.Match(domain):
case r.DirectRule.Match(domain):
case r.proxyRule.Match(domain):
_ = w.WriteMsg(r.dnsProxyA(domain, r.dns.serveIP, req))
case r.ProxyRule.Match(domain):
host, _, err := net.SplitHostPort(w.LocalAddr().String())
if err != nil {
slog.Warn("proxy dns", "error", err, "host", host)
} else {
slog.Debug("proxy dns", "host", host)
}
_ = w.WriteMsg(r.dnsProxyA(domain, net.ParseIP(host), req))
return
}
// 2. direct with cache, do not fallback to proxy to avoid side-effect
c := &dnsCache{Router: r, Req: req}
if err := r.dns.cache.Remember(c, req.Question[0].String()); err != nil {
resp, err := r.Exchange(req)
if err != nil {
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeServerFailure))
return
} else {
_ = w.WriteMsg(resp)
}
c.Resp.SetReply(req)
c.Resp.Compress = true
_ = w.WriteMsg(c.Resp)
}
func (r *Router) dnsFail(req *dns.Msg, rcode int) *dns.Msg {
m := new(dns.Msg)
m.SetRcode(req, dns.RcodeServerFailure)
m.SetRcode(req, rcode)
return m
}
@@ -56,7 +60,6 @@ func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
A: localIP,
}}
} else {
m.Answer = []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 20},
@@ -66,28 +69,43 @@ func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg
return m
}
type dnsCache struct {
*Router
Req, Resp *dns.Msg
}
var (
upstreamAddrs []string
upstreamIndex int32 = -1
queryCount int32 = 0
)
func (r *dnsCache) Fulfill(question string) (err error) {
conn := <-r.dns.connCh
func (r *Router) Exchange(req *dns.Msg) (_ *dns.Msg, err error) {
atomic.AddInt32(&queryCount, 1)
if atomic.LoadInt32(&upstreamIndex) < 0 {
dnsIPs := []string{r.dns.upstreamDNS}
if r.dns.upstreamDNS == "" {
dnsIPs, err = dhcp.GetDNSServer()
slog.Error("get dns server", "error", err, "queryCount", atomic.LoadInt32(&queryCount), "dns", dnsIPs)
}
var rtt time.Duration
r.Resp, rtt, err = r.dns.ExchangeWithConn(r.Req, conn)
addrs := make([]string, 0, len(dnsIPs)+1)
addrs = append(addrs, net.JoinHostPort(r.dns.fallbackDNS, "53"))
for _, ip := range dnsIPs {
if ip != string(r.dns.serveIP) {
addrs = append(addrs, net.JoinHostPort(ip, "53"))
}
}
atomic.StoreInt32(&queryCount, 0)
upstreamAddrs = addrs
atomic.StoreInt32(&upstreamIndex, int32(len(addrs)-1))
slog.Info("use upstream dns", "ip", upstreamAddrs[atomic.LoadInt32(&upstreamIndex)])
}
resp, err := dns.Exchange(req, upstreamAddrs[atomic.LoadInt32(&upstreamIndex)])
if err != nil {
r.Resp, rtt, err = r.dns.ExchangeWithConn(r.Req, conn)
atomic.AddInt32(&upstreamIndex, -1)
if atomic.LoadInt32(&upstreamIndex) >= 0 {
slog.Info("use upstream dns", "ip", upstreamAddrs[atomic.LoadInt32(&upstreamIndex)])
resp, err = dns.Exchange(req, upstreamAddrs[atomic.LoadInt32(&upstreamIndex)])
}
}
log.DebugWarn(err).
Dur("rtt", rtt).
Str("question", question).
Msg("exchange dns record")
select {
case r.dns.connCh <- conn:
default:
conn.Close()
}
return err
return resp, err
}
+10 -15
View File
@@ -1,16 +1,15 @@
package router
import (
"log/slog"
"net/http"
"sync"
"time"
"github.com/wweir/deferlog/log"
"github.com/sower-proxy/mem"
)
var pingClient = http.Client{
Timeout: 2 * time.Second,
}
var accessCache = mem.NewRotateCache(time.Hour, httpPing)
func (r *Router) isAccess(domain string, port uint16) bool {
switch port {
@@ -20,16 +19,15 @@ func (r *Router) isAccess(domain string, port uint16) bool {
return false
}
p := &ping{}
_ = r.accessCache.Remember(p, domain)
return p.isAccess
ok, _ := accessCache.Get(domain)
return ok
}
type ping struct {
isAccess bool
var pingClient = http.Client{
Timeout: 2 * time.Second,
}
func (p *ping) Fulfill(key string) error {
func httpPing(key string) (bool, error) {
wg := sync.WaitGroup{}
wg.Add(2)
var err80, err443 error
@@ -44,11 +42,8 @@ func (p *ping) Fulfill(key string) error {
wg.Wait()
if err80 != nil && err443 != nil {
log.Warn().
Errs("errs", []error{err80, err443}).
Msg("Failed to ping")
slog.Warn("failed to ping", "err80", err80, "err443", err443)
}
p.isAccess = (err80 == nil && err443 == nil)
return nil
return err80 == nil && err443 == nil, nil
}
+39 -73
View File
@@ -1,6 +1,7 @@
package router
import (
"log/slog"
"net"
"strconv"
"time"
@@ -8,101 +9,66 @@ import (
"github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
"github.com/pkg/errors"
"github.com/wweir/deferlog"
"github.com/wweir/deferlog/log"
"github.com/wweir/sower/pkg/dhcp"
"github.com/wweir/sower/pkg/mem"
"github.com/wweir/sower/util"
"github.com/sower-proxy/conns/relay"
"github.com/sower-proxy/deferlog/v2"
"github.com/wweir/sower/pkg/suffixtree"
)
type ProxyDialFn func(network, host string, port uint16) (net.Conn, error)
type Router struct {
blockRule *util.Node
directRule *util.Node
proxyRule *util.Node
ProxyDial ProxyDialFn
accessCache *mem.Cache
type (
ProxyDialFn func(network, host string, port uint16) (net.Conn, error)
Router struct {
BlockRule *suffixtree.Node
DirectRule *suffixtree.Node
ProxyRule *suffixtree.Node
ProxyDial ProxyDialFn
dns struct {
dns.Client
fallbackDNS string
serveIP net.IP
connCh chan *dns.Conn
cache *mem.Cache
dns struct {
dns.Client
upstreamDNS string
fallbackDNS string
serveIP net.IP
}
country struct {
*geoip2.Reader
cidrs []*net.IPNet
}
}
)
country struct {
*geoip2.Reader
cidrs []*net.IPNet
}
}
func NewRouter(serveIP, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
func NewRouter(serveIP, upstreamDNS, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
r := Router{
ProxyDial: proxyDial,
accessCache: mem.New(time.Hour), // TODO: config
ProxyDial: proxyDial,
}
r.dns.upstreamDNS = upstreamDNS
r.dns.serveIP = net.ParseIP(serveIP)
r.dns.fallbackDNS = fallbackDNS
r.dns.connCh = make(chan *dns.Conn, 1)
r.dns.cache = mem.New(5 * time.Minute) // Tll: 10 minutes
go r.dialDNSConn()
var err error
r.country.Reader, err = geoip2.Open(mmdbFile)
log.InfoWarn(err).Str("file", mmdbFile).Msg("open geoip2 db")
if err != nil {
slog.Warn("open geoip2 db", "error", err, "file", mmdbFile)
}
return &r
}
func (r *Router) SetRules(blockList, directList, proxyList, directCIDRs []string) {
r.blockRule = util.NewNodeFromRules(blockList...)
r.directRule = util.NewNodeFromRules(directList...)
r.proxyRule = util.NewNodeFromRules(proxyList...)
r.country.cidrs = make([]*net.IPNet, 0, len(directCIDRs))
for _, cidr := range directCIDRs {
func (r *Router) AddCountryCIDRs(cidrs ...string) {
for _, cidr := range cidrs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
log.Error().Err(err).Msg("Failed to parse CIDR")
slog.Error("failed to parse CIDR", "error", err)
}
r.country.cidrs = append(r.country.cidrs, ipnet)
}
}
func (r *Router) dialDNSConn() {
for {
server, err := dhcp.GetDNSServer()
log.Err(err).
Str("DNS", server).
Str("fallback", r.dns.fallbackDNS).
Msg("get DNS server")
if server == "" {
server = r.dns.fallbackDNS
}
for {
conn, err := dns.DialTimeout("udp", net.JoinHostPort(server, "53"), time.Second)
if err != nil {
log.Error().Err(err).Str("ip", server).Msg("dial dns server")
break
}
r.dns.connCh <- conn
}
}
r.country.cidrs = suffixtree.GCSlice(r.country.cidrs)
}
func (r *Router) RouteHandle(conn net.Conn, domain string, port uint16) (err error) {
start := time.Now()
defer func() {
deferlog.DebugWarn(err).
Str("domain", domain).
Uint16("port", port).
Dur("spend", time.Since(start)).
Msg("serve socks5")
deferlog.DebugWarn(err, "route handle", "domain", domain, "port", port, "took", time.Since(start))
}()
addr := net.JoinHostPort(domain, strconv.FormatUint(uint64(port), 10))
@@ -111,13 +77,13 @@ func (r *Router) RouteHandle(conn net.Conn, domain string, port uint16) (err err
// 2. detect_based( CN IP || access site )
// 3. fallback( proxy )
switch {
case r.blockRule.Match(domain):
case r.BlockRule.Match(domain):
return nil
case r.directRule.Match(domain):
case r.DirectRule.Match(domain):
return r.DirectHandle(conn, addr)
case r.proxyRule.Match(domain):
case r.ProxyRule.Match(domain):
return r.ProxyHandle(conn, domain, port)
case r.localSite(domain), r.isAccess(domain, port):
@@ -135,11 +101,11 @@ func (r *Router) ProxyHandle(conn net.Conn, domain string, port uint16) error {
}
defer rc.Close()
util.Relay(conn, rc)
_ = relay.Relay(conn, rc)
return nil
}
func (r *Router) DirectHandle(conn net.Conn, addr string) error {
dur, err := util.RelayTo(conn, addr)
dur, err := relay.RelayTo(conn, addr)
return errors.Wrapf(err, "spend (%s)", dur)
}
+28
View File
@@ -0,0 +1,28 @@
package router
import (
"net"
"net/url"
"strconv"
)
func ParseHostPort(hostport string, u *url.URL) (string, uint16, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
if err.(*net.AddrError).Err == "missing port in address" {
switch u.Scheme {
case "http":
return hostport, 80, nil
case "https":
return hostport, 443, nil
}
}
return "", 0, err
}
portInt, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return "", 0, err
}
return host, uint16(portInt), nil
}
+6 -2
View File
@@ -17,7 +17,7 @@ type authReq struct {
func (req *authReq) Fulfill(r io.Reader) error {
buf := make([]byte, 2)
if n, err := r.Read(buf); err != nil || n != 2 {
if n, err := io.ReadFull(r, buf); err != nil || n != 2 {
return err
}
@@ -25,7 +25,7 @@ func (req *authReq) Fulfill(r io.Reader) error {
req.NMETHODS = buf[1]
req.METHODS = make([]byte, int(req.NMETHODS))
if n, err := r.Read(req.METHODS); err != nil || n != len(req.METHODS) {
if n, err := io.ReadFull(r, req.METHODS); err != nil || n != len(req.METHODS) {
return err
}
@@ -70,6 +70,7 @@ type addrType interface {
}
// ATYP:
//
// 0x01 -> net.IPv4len
// 0x03 -> first byte is length
// 0x04 -> net.IPv6len
@@ -81,6 +82,7 @@ type addrTypeIPv4 struct {
func (a *addrTypeIPv4) Fulfill(r io.Reader) error {
return binary.Read(r, binary.BigEndian, a)
}
func (a *addrTypeIPv4) Addr() (string, uint16) {
return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT
}
@@ -108,6 +110,7 @@ func (a *addrTypeDomain) Fulfill(r io.Reader) error {
return nil
}
func (a *addrTypeDomain) Addr() (string, uint16) {
return string(a.DST_ADDR[:]), a.DST_PORT
}
@@ -120,6 +123,7 @@ type addrTypeIPv6 struct {
func (a *addrTypeIPv6) Fulfill(r io.Reader) error {
return binary.Read(r, binary.BigEndian, a)
}
func (a *addrTypeIPv6) Addr() (string, uint16) {
return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT
}
+7 -5
View File
@@ -28,14 +28,16 @@ func New() *Socks5 {
return &Socks5{}
}
var noAuthResp = authResp{VER: 5, METHOD: 0}
var succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1}
var (
noAuthResp = authResp{VER: 5, METHOD: 0}
succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1}
)
func (s *Socks5) Unwrap(conn net.Conn) (net.Addr, error) {
{ //auth
{ // auth
auth := new(authReq)
if err := auth.Fulfill(conn); err != nil && !auth.IsValid() {
return nil, errors.Errorf("read head: %v, err: %s", auth, err)
if err := auth.Fulfill(conn); err != nil || !auth.IsValid() {
return nil, errors.Errorf("read auth head: %v, err: %s", auth, err)
}
if err := binary.Write(conn, binary.BigEndian, noAuthResp); err != nil {
+2 -7
View File
@@ -5,16 +5,11 @@ import (
"strings"
"testing"
"github.com/wweir/deferlog/log"
"github.com/wweir/sower/pkg/teeconn"
"github.com/sower-proxy/conns/reread"
"github.com/wweir/sower/transport/sower"
"github.com/wweir/sower/transport/trojan"
)
func init() {
log.Logger = log.Logger.With().Caller().Logger()
}
func testPipe(tran Transport) (net.Addr, error) {
r, w := net.Pipe()
defer r.Close()
@@ -24,7 +19,7 @@ func testPipe(tran Transport) (net.Addr, error) {
tran.Wrap(w, "sower", 443)
}(w)
return tran.Unwrap(teeconn.New(r))
return tran.Unwrap(reread.New(r))
}
func Test_Transports(t *testing.T) {
+5 -4
View File
@@ -71,15 +71,16 @@ func (*domain) Network() string { return "tcp" }
func (a *domain) String() string {
return net.JoinHostPort(a.ADDR, strconv.Itoa(int(a.PORT)))
}
func (a *domain) Fulfill(r io.Reader) error {
buf := make([]byte, 1)
if n, err := r.Read(buf); err != nil || n != 1 {
if n, err := io.ReadFull(r, buf); err != nil || n != 1 {
return errors.New("read domain length failed")
}
addrLen := int(buf[0])
buf = make([]byte, addrLen+4)
if n, err := r.Read(buf); err != nil || n != addrLen+4 {
if n, err := io.ReadFull(r, buf); err != nil || n != addrLen+4 {
return errors.Wrap(err, "read doamin failed")
}
@@ -127,12 +128,12 @@ func (t *Trojan) Unwrap(conn net.Conn) (net.Addr, error) {
head.CMD, head.ATYP = buf[58], buf[59]
switch head.ATYP {
case 0x01: //ipv4
case 0x01: // ipv4
addr := &ipv4Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
case 0x04: //ipv6
case 0x04: // ipv6
addr := &ipv6Addr{}
err := binary.Read(conn, binary.BigEndian, addr)
return addr, errors.Wrap(err, "read addr")
-50
View File
@@ -1,50 +0,0 @@
package util
import (
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
)
func RelayTo(conn net.Conn, addr string) (dur time.Duration, err error) {
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = net.JoinHostPort(addr, "80")
}
start := time.Now()
rc, err := net.DialTimeout("tcp", addr, 5*time.Second)
if err != nil {
return time.Since(start), errors.WithStack(err)
}
defer rc.Close()
Relay(conn, rc)
return time.Since(start), nil
}
func Relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)
wg.Add(2)
go redirect(conn2, conn1, wg, exitFlag)
redirect(conn1, conn2, wg, exitFlag)
wg.Wait()
}
func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) {
// io.Copy(dst, io.TeeReader(src, os.Stdout))
io.Copy(dst, src)
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
// wakeup blocked goroutine
now := time.Now()
src.SetDeadline(now)
dst.SetDeadline(now)
}
wg.Done()
}
-108
View File
@@ -1,108 +0,0 @@
package util
import (
"strings"
"sync"
)
type Node struct {
node
sep string
*sync.RWMutex
}
type node struct {
node map[string]*node
}
func NewNodeFromRules(rules ...string) *Node {
n := &Node{node{node: map[string]*node{}}, ".", &sync.RWMutex{}}
for i := range rules {
n.Add(rules[i])
}
return n
}
func (n *Node) String() string {
n.RLock()
defer n.RUnlock()
return n.string("", " ")
}
func (n *node) string(prefix, indent string) (out string) {
for key, val := range n.node {
out += prefix + key + "\n" + val.string(prefix+indent, indent)
}
return
}
func (n *Node) trim(item string) string {
return strings.TrimSuffix(item, n.sep)
}
func (n *Node) Add(item string) {
n.Lock()
defer n.Unlock()
n.add(strings.Split(n.trim(item), n.sep))
}
func (n *node) add(secs []string) {
length := len(secs)
switch length {
case 0:
return
case 1:
n.node[secs[length-1]] = &node{node: map[string]*node{"": {}}}
default:
sec := secs[length-1]
if sec == "**" {
sec = "*"
}
subNode, ok := n.node[sec]
if !ok {
subNode = &node{node: map[string]*node{}}
n.node[sec] = subNode
}
subNode.add(secs[:length-1])
}
}
func (n *Node) Match(item string) bool {
if n == nil {
return false
}
n.RLock()
defer n.RUnlock()
return n.matchSecs(strings.Split(n.trim(item), n.sep), false)
}
func (n *node) matchSecs(secs []string, fuzzNode bool) bool {
length := len(secs)
if length == 0 {
if _, ok := n.node[""]; ok {
return true
}
if _, ok := n.node["**"]; ok {
return true
}
if _, ok := n.node["*"]; ok {
return !fuzzNode
}
return false
}
if n, ok := n.node[secs[length-1]]; ok {
if n.matchSecs(secs[:length-1], false) {
return true
}
}
if n, ok := n.node["*"]; ok {
if n.matchSecs(secs[:length-1], true) {
return true
}
}
if _, ok := n.node["**"]; ok {
return true
}
return false
}