mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62176ffb65 | ||
|
|
5fe202314d | ||
|
|
fc7183d70e | ||
|
|
4a9197a4ec | ||
|
|
9eaf27fdcc | ||
|
|
ea6c2a29ad | ||
|
|
77c4154c7c | ||
|
|
eadb2f0bdf | ||
|
|
19b61945ad | ||
|
|
3d6ee5e376 | ||
|
|
c0f704a5ad | ||
|
|
ac09663bc4 | ||
|
|
45c9eea6e6 | ||
|
|
0c83462b91 | ||
|
|
15c02b55d8 | ||
|
|
aca4636433 | ||
|
|
d0d635f8b0 | ||
|
|
bde3b1bfa4 | ||
|
|
5dfefdf117 | ||
|
|
3f63038631 | ||
|
|
8d18bd6c5b | ||
|
|
98a5f5702c | ||
|
|
2948ffb7db | ||
|
|
b85c3a63d7 | ||
|
|
47e8076c06 | ||
|
|
10dc20e9e8 | ||
|
|
19dcf8e0d7 | ||
|
|
795bf73fb2 | ||
|
|
3412643b6e | ||
|
|
6fc97a3c9f | ||
|
|
78d36b0c19 | ||
|
|
5ebd33b0d7 | ||
|
|
b1f152cc0c | ||
|
|
c0b77da05a | ||
|
|
bad7ae702a | ||
|
|
05a1a3249a | ||
|
|
0f029f8c15 | ||
|
|
8c9a0bc1dd | ||
|
|
465ff316fd |
+21
-2
@@ -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
@@ -15,6 +15,6 @@
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/usr/local/etc</string>
|
||||
<string>/usr/local/etc/sower</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
.vscode
|
||||
.idea
|
||||
**.env
|
||||
/bin
|
||||
|
||||
|
||||
**.mmdb
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
@@ -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{}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -0,0 +1,6 @@
|
||||
package suffixtree
|
||||
|
||||
func GCSlice[T any](arr []T) []T {
|
||||
old := arr
|
||||
return append(make([]T, 0, len(old)), old...)
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user