mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
a86de61a45 | ||
|
|
ffca4220d6 | ||
|
|
48e9680c03 | ||
|
|
dabcf998c9 | ||
|
|
133684e5d2 | ||
|
|
a28a211d2d | ||
|
|
28696acafb | ||
|
|
38c7391ceb | ||
|
|
65234b1666 | ||
|
|
adca6606d2 | ||
|
|
cfc1afb710 | ||
|
|
397825556c | ||
|
|
8694ff6edd | ||
|
|
a38affd97b | ||
|
|
74202aad83 | ||
|
|
59dfa934a2 | ||
|
|
8bfb26ba3f | ||
|
|
fbc6c93395 | ||
|
|
2d269468ea | ||
|
|
d7c0ba112e | ||
|
|
d31ba7ef79 | ||
|
|
8d32bca742 | ||
|
|
426799a961 | ||
|
|
5cf8df2a7a | ||
|
|
7d024f3bfe | ||
|
|
585cc01744 | ||
|
|
8307128844 | ||
|
|
dab1a78bbf | ||
|
|
13a6be47a4 | ||
|
|
56b8e35fe7 | ||
|
|
47b8fc601f | ||
|
|
2dc128d1c8 | ||
|
|
2a4776dc28 | ||
|
|
d121427bbb | ||
|
|
91582d6095 | ||
|
|
396b08c0ac | ||
|
|
78b6d4df2e | ||
|
|
1574623507 | ||
|
|
ee6335fa47 | ||
|
|
f7214bfdb4 | ||
|
|
291ae3f85a | ||
|
|
07c51d42ff | ||
|
|
90c497b8da | ||
|
|
28bd12d220 | ||
|
|
c0f39fc2e0 | ||
|
|
bcc6d7c504 | ||
|
|
5d1827b2da | ||
|
|
c38d519f92 | ||
|
|
dd4444b71c | ||
|
|
899d64a92c | ||
|
|
7d77f4a85e | ||
|
|
4681dd9845 | ||
|
|
85226890c5 | ||
|
|
1a24286bbc | ||
|
|
fd5c34dca1 | ||
|
|
8cd4fe57ba | ||
|
|
545b153f50 | ||
|
|
697e2aa1d5 | ||
|
|
2fe1708d3f | ||
|
|
cbe943a020 | ||
|
|
d7e25d6565 | ||
|
|
acb3f58526 | ||
|
|
50684272c9 | ||
|
|
b0428177de | ||
|
|
75e7f1aab7 | ||
|
|
f256ab686d | ||
|
|
0c0f0699c1 | ||
|
|
8532a2e16c | ||
|
|
bad338033c | ||
|
|
a57f85ab1b | ||
|
|
aed2afbe6d | ||
|
|
2becd42176 | ||
|
|
d3664dc2b9 | ||
|
|
a6fd60b5a2 | ||
|
|
d6dbfd4129 | ||
|
|
d31bbf9226 | ||
|
|
ac4e4266bc | ||
|
|
f9f0ca4ee7 | ||
|
|
98c3934762 | ||
|
|
f001f7dab7 | ||
|
|
36ef329b68 | ||
|
|
64778f68ac | ||
|
|
53748cfa85 | ||
|
|
d35115b7b3 | ||
|
|
559c5fde6b | ||
|
|
eb8080df18 | ||
|
|
45ca063a7e | ||
|
|
6add7afd19 | ||
|
|
f13687506e | ||
|
|
199e4cf295 | ||
|
|
b3ad9f6657 | ||
|
|
f9e9fe366f | ||
|
|
103699e25e | ||
|
|
f80755eae5 | ||
|
|
5b217b794d | ||
|
|
26055c5203 | ||
|
|
029611c078 | ||
|
|
89adc6a2c9 | ||
|
|
8c95c59e5e | ||
|
|
4f8376c041 | ||
|
|
10172f8be2 | ||
|
|
2831cdf284 | ||
|
|
06558ac635 | ||
|
|
3cf4deddb7 | ||
|
|
679263be9a | ||
|
|
21d8ec4ec7 | ||
|
|
4916245522 | ||
|
|
11c2808320 | ||
|
|
21ddd79e4d | ||
|
|
9a158d4088 | ||
|
|
88990cddd9 | ||
|
|
ca1b2650ab | ||
|
|
8ff1046ebf | ||
|
|
dfe81f6f23 | ||
|
|
972875ec36 | ||
|
|
89b7a41d46 | ||
|
|
8f598dbc17 | ||
|
|
192b4f798b | ||
|
|
2adefe0dfc | ||
|
|
7b0c13ef1c | ||
|
|
63bb934148 | ||
|
|
cacc732a4e | ||
|
|
17d079dcb2 | ||
|
|
413a93f6f7 | ||
|
|
6c5f5cd74f | ||
|
|
e948541efe | ||
|
|
ffa2128274 | ||
|
|
b53b7874b6 | ||
|
|
f3117edfa0 | ||
|
|
ba8980da73 | ||
|
|
e3ec74b74f | ||
|
|
6e67193894 | ||
|
|
af0c5f895c | ||
|
|
4883e240cc | ||
|
|
18ee9ca993 | ||
|
|
4fff2a1f3b | ||
|
|
3e82890c33 | ||
|
|
01d66d737b | ||
|
|
e342dadaf1 | ||
|
|
7ba9f1a23c | ||
|
|
7240208cb3 | ||
|
|
a27748d835 | ||
|
|
39797d077b | ||
|
|
b6df69358a | ||
|
|
ae525f9b40 | ||
|
|
29eb0e6e1c | ||
|
|
aef941b17e | ||
|
|
9bc5a32074 | ||
|
|
8194083f54 | ||
|
|
c20903137d | ||
|
|
2f3c98b8d3 | ||
|
|
bc99bda1b3 | ||
|
|
ceedef4203 | ||
|
|
712b689708 | ||
|
|
d5260d66df | ||
|
|
bde27f64ec | ||
|
|
04193eb608 | ||
|
|
41a1adc707 | ||
|
|
c9191ec928 | ||
|
|
14da5b711e | ||
|
|
59257c0673 | ||
|
|
7db92a19be | ||
|
|
fbae2999dc | ||
|
|
92991da6a0 | ||
|
|
b60b5fceb3 | ||
|
|
756a8c8d17 | ||
|
|
e02da26936 | ||
|
|
c5adcc4421 | ||
|
|
c20c3621c6 | ||
|
|
055c1a2901 | ||
|
|
fc2ea8b8c0 | ||
|
|
3e6b9e96ca | ||
|
|
2264d73c45 | ||
|
|
99ba06b2d4 | ||
|
|
a8e026ff7f | ||
|
|
9e377e4a2d | ||
|
|
107174fb6b | ||
|
|
3f8f5c2ef1 | ||
|
|
918b964b78 | ||
|
|
34722a460c | ||
|
|
8734486c48 | ||
|
|
8d0ee2a9ea | ||
|
|
98247845ad | ||
|
|
72dacaf1de | ||
|
|
56d5090841 | ||
|
|
821cbc302e | ||
|
|
f6eddb9f96 | ||
|
|
344531c301 | ||
|
|
df897f72c6 | ||
|
|
dd2fe95900 | ||
|
|
08a5d4363b | ||
|
|
e712dcb78c | ||
|
|
e9f5fc4789 | ||
|
|
f76f31d054 | ||
|
|
e0de537042 |
@@ -0,0 +1,100 @@
|
||||
# 远程、上游连接方式
|
||||
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,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>sower</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/sower</string>
|
||||
<string>-f</string>
|
||||
<string>/usr/local/etc/sower/sower.hcl</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/usr/local/etc/sower</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=sower client service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
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"
|
||||
@@ -0,0 +1 @@
|
||||
CreateObject("WScript.Shell").Run "C:\Software\Sower\sower.exe -f sower.hcl",0
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=sowerd server service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
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
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Go
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.17
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: test and build
|
||||
run: |
|
||||
make
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.18
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: build matrix
|
||||
run: |
|
||||
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.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.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.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.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.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.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.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.yaml sower.toml sower_run.vbs
|
||||
make clean
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "*.tar.gz"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+17
-6
@@ -12,11 +12,22 @@
|
||||
*.out
|
||||
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
._*
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode
|
||||
.idea
|
||||
sower
|
||||
cert.pem
|
||||
**/cert.pem
|
||||
privkey.pem
|
||||
**/privkey.pem
|
||||
**.env
|
||||
/bin
|
||||
|
||||
|
||||
**.mmdb
|
||||
/cmd/client/client
|
||||
/cmd/server/server
|
||||
/sower
|
||||
/sowerd
|
||||
/*.yml
|
||||
/*.yaml
|
||||
/*.toml
|
||||
/*.hcl
|
||||
/*.tf
|
||||
@@ -1,373 +1,22 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
MIT License
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
Copyright (c) 2020 wweir
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
SERVER:=127.0.0.1:5533
|
||||
MAKEFLAGS += --jobs all
|
||||
GO:=CGO_ENABLED=0 go
|
||||
|
||||
default: build
|
||||
default: test build
|
||||
|
||||
generate:
|
||||
go generate ./...
|
||||
test:
|
||||
${GO} vet ./...
|
||||
${GO} test ./...
|
||||
|
||||
build:
|
||||
GOBIN=$(PWD) go install -v
|
||||
build: sower sowerd
|
||||
.PHONY: sower
|
||||
sower:
|
||||
${GO} build -ldflags "\
|
||||
-X main.version=$(shell git describe --tags --always) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)" \
|
||||
-o sower ./cmd/sower
|
||||
.PHONY: sowerd
|
||||
sowerd:
|
||||
${GO} build -ldflags "\
|
||||
-X main.version=$(shell git describe --tags --always) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)" \
|
||||
-o sowerd ./cmd/sowerd
|
||||
|
||||
kill:
|
||||
sudo pkill -9 sower || true
|
||||
|
||||
client: build kill
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr
|
||||
|
||||
server: build kill
|
||||
$(PWD)/sower -n QUIC -logtostderr -v 1
|
||||
|
||||
run: build kill
|
||||
$(PWD)/sower -n QUIC -logtostderr -v 1 &
|
||||
sudo $(PWD)/sower -f conf/sower.toml -logtostderr &
|
||||
sleep 1
|
||||
@curl 127.0.0.1
|
||||
sleep 1
|
||||
sudo pkill -9 sower || true
|
||||
clean:
|
||||
rm -f sower sowerd sower.exe sowerd.exe
|
||||
@@ -1,48 +1,72 @@
|
||||
# sower
|
||||
Yet another cross platform transparent proxy tool
|
||||
|
||||
## architecture
|
||||
```
|
||||
request target servers
|
||||
<-------------+ +------------->
|
||||
| |
|
||||
| |
|
||||
+------------server-------------+
|
||||
| | relay service| |
|
||||
| +-----+---------------------+ |
|
||||
| | | |
|
||||
| | parsr http(s) target url | |
|
||||
| | | |
|
||||
| +---------------------------+ |
|
||||
| |
|
||||
+--------^----------------------+
|
||||
| request domain server
|
||||
quic connection +---------->
|
||||
| |
|
||||
+--------+---client+------+-----+
|
||||
| | |
|
||||
| | |
|
||||
| relay service | dns |
|
||||
| | server |
|
||||
| | |
|
||||
| 127.0.0.1 or other |
|
||||
| | |
|
||||
+-------^----------+---^----^---+
|
||||
| | |
|
||||
| | | +----->
|
||||
| +----------+ | |
|
||||
2 1 1 2
|
||||
+ + + +
|
||||
block request normal request
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://github.com/wweir/sower/actions)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
|
||||
```
|
||||
中文介绍见 [Wiki](https://github.com/wweir/sower/wiki)
|
||||
|
||||
## install
|
||||
1. install server on `server node` by `make server`
|
||||
2. write config file, example: [conf/sower.toml](https://github.com/wweir/sower/blob/master/conf/sower.toml)
|
||||
3. install client on `client node` by `make client`
|
||||
4. add `127.0.0.1` as you first domain server
|
||||
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.
|
||||
|
||||
## todo
|
||||
- [ ] authenticate
|
||||
- [ ] broker
|
||||
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 sower, you need to deploy sower on both server-side(sowerd) and client-side(sower).
|
||||
|
||||
## Sowerd
|
||||
|
||||
_If you want to use sower as a secondary proxy to provide intelligent routing, you can skip sowerd._
|
||||
|
||||
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 sowerd from [`Let's Encrypt`](https://letsencrypt.org/).
|
||||
|
||||
There are two ways to run the sowerd service:
|
||||
|
||||
1. Run the command with root permission
|
||||
|
||||
```shell
|
||||
# sowerd -password XXX -fake_site 127.0.0.1:8080
|
||||
```
|
||||
|
||||
2. Install as a systemd service
|
||||
|
||||
```service
|
||||
[Unit]
|
||||
Description=sower server service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/sowerd
|
||||
Environment="FAKE_SITE=127.0.0.1:8080"
|
||||
Environment="PASSWORD=XXX"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## Sower
|
||||
|
||||
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 bind 4 ports by default with root permission. They are: `udp(53)` / `tcp(80)` / `tcp(443)` / `tcp(1080)`.
|
||||
|
||||
After doing the next three steps, you can enjoy the intelligent transparent proxy solution:
|
||||
|
||||
1. Run the command with root permission:
|
||||
|
||||
```shell
|
||||
# sower -f sower.hcl
|
||||
```
|
||||
|
||||
2. Change your DNS server to `127.0.0.1`.
|
||||
|
||||
3. Set your proxy to `socks5h://127.0.0.1:1080`.
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cristalhq/aconfig"
|
||||
"github.com/cristalhq/aconfig/aconfighcl"
|
||||
"github.com/cristalhq/aconfig/aconfigtoml"
|
||||
"github.com/cristalhq/aconfig/aconfigyaml"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/pkg/suffixtree"
|
||||
"github.com/wweir/sower/router"
|
||||
)
|
||||
|
||||
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"`
|
||||
Password string `usage:"remote proxy password"`
|
||||
}
|
||||
|
||||
DNS struct {
|
||||
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"`
|
||||
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:"127.0.0.1:1080" usage:"socks5 listen address"`
|
||||
} `flag:"socks5"`
|
||||
|
||||
Router struct {
|
||||
Block struct {
|
||||
File string `usage:"block list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"block list rules"`
|
||||
}
|
||||
Direct struct {
|
||||
File string `usage:"direct list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"direct list rules"`
|
||||
}
|
||||
Proxy struct {
|
||||
File string `usage:"proxy list file, local file or remote"`
|
||||
FilePrefix string `default:"**." usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"proxy list rules"`
|
||||
}
|
||||
|
||||
Country struct {
|
||||
MMDB string `usage:"mmdb file"`
|
||||
File string `usage:"CIDR block list file, local file or remote"`
|
||||
FilePrefix string `default:"" usage:"parsed as '<prefix>line_text'"`
|
||||
Rules []string `usage:"CIDR list rules"`
|
||||
}
|
||||
}
|
||||
}{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := aconfig.LoaderFor(&conf, aconfig.Config{
|
||||
AllowUnknownFields: true,
|
||||
FileFlag: "f",
|
||||
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(),
|
||||
".toml": aconfigtoml.New(),
|
||||
".hcl": aconfighcl.New(),
|
||||
},
|
||||
}).Load(); err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Interface("config", conf).
|
||||
Msg("Load config")
|
||||
}
|
||||
|
||||
if conf.DNS.ServeIface != "" {
|
||||
iface, err := net.InterfaceByName(conf.DNS.ServeIface)
|
||||
log.DebugFatal(err).Str("iface", conf.DNS.ServeIface).Msg("get iface")
|
||||
addrs, err := iface.Addrs()
|
||||
log.InfoFatal(err).Str("iface", conf.DNS.ServeIface).Msg("get iface addrs")
|
||||
for _, addr := range addrs {
|
||||
ip, _, err := net.ParseCIDR(addr.String())
|
||||
log.InfoFatal(err).Str("iface", conf.DNS.ServeIface).
|
||||
Msg("parse iface IP: " + ip.String())
|
||||
|
||||
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 == "" {
|
||||
log.Fatal().Msg("dns serve ip and serve inteface not setted")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func main() {
|
||||
proxyDial := GenProxyDial(conf.Remote.Type, conf.Remote.Addr, conf.Remote.Password, conf.DNS.Fallback)
|
||||
r := router.NewRouter(conf.DNS.Serve, 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...)
|
||||
|
||||
if conf.DNS.Disable {
|
||||
log.Info().Msg("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)
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
lnHTTP, err := net.Listen("tcp", net.JoinHostPort(ip, "80"))
|
||||
log.DebugFatal(err).Str("listen_on", net.JoinHostPort(ip, "80")).Msg("listen port 80")
|
||||
go ServeHTTP(lnHTTP, r)
|
||||
|
||||
lnHTTPS, err := net.Listen("tcp", net.JoinHostPort(ip, "443"))
|
||||
log.DebugFatal(err).Str("listen_on", net.JoinHostPort(ip, "443")).Msg("listen port 443")
|
||||
go ServeHTTPS(lnHTTPS, r)
|
||||
|
||||
go func(ip string) {
|
||||
err := dns.ListenAndServe(net.JoinHostPort(ip, "53"), "udp", r)
|
||||
log.InfoFatal(err).Str("listen_on", ip).Msg("serve dns")
|
||||
}(ip)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
if conf.Socks5.Disable {
|
||||
log.Info().Msg("SOCKS5 proxy disabled")
|
||||
return
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", conf.Socks5.Addr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("listen port")
|
||||
}
|
||||
log.Info().Msgf("SOCKS5 proxy listening on %s", conf.Socks5.Addr)
|
||||
go ServeSocks5(ln, r)
|
||||
}()
|
||||
|
||||
start := time.Now()
|
||||
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("took", time.Since(start)).
|
||||
Uint64("blockRule", r.BlockRule.Count).
|
||||
Uint64("directRule", r.DirectRule.Count).
|
||||
Uint64("proxyRule", r.ProxyRule.Count).
|
||||
Msg("Loaded rules, proxy started")
|
||||
runtime.GC()
|
||||
select {}
|
||||
}
|
||||
|
||||
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 := 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{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
domain, port, _ := net.SplitHostPort(addr)
|
||||
p, _ := strconv.Atoi(port)
|
||||
return proxyDial("tcp", domain, uint16(p))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
loadFn = func() (io.ReadCloser, error) {
|
||||
req, _ := http.NewRequest(http.MethodGet, file, nil)
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, errors.Errorf("status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
}
|
||||
|
||||
// load rule file, retry 10 times
|
||||
rc, err := loadFn()
|
||||
for i := time.Duration(1); i < 10; i++ {
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// wait: 28.5s
|
||||
time.Sleep(i * i * 100 * time.Millisecond)
|
||||
rc, err = loadFn()
|
||||
}
|
||||
log.InfoFatal(err).
|
||||
Str("file", file).
|
||||
Msg("fetch rule file")
|
||||
|
||||
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 {
|
||||
log.Error().Err(err).
|
||||
Str("file", file).
|
||||
Msg("read line")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(line)) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ch <- string(line)
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sower-proxy/conns/relay"
|
||||
"github.com/sower-proxy/conns/reread"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"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"
|
||||
)
|
||||
|
||||
func GenProxyDial(proxyType, proxyHost, proxyPassword, dnsFallback 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(dnsFallback, "53"))
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("dial fallback dns failed, use default dns setting")
|
||||
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.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.DialWithDialer(dialer, "tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg)
|
||||
}
|
||||
|
||||
case "socks5":
|
||||
proxy = socks5.New()
|
||||
dialFn = func() (net.Conn, error) {
|
||||
return dialer.Dial("tcp", proxyHost)
|
||||
}
|
||||
|
||||
default:
|
||||
log.Fatal().
|
||||
Str("type", conf.Remote.Type).
|
||||
Msg("unknown proxy type")
|
||||
}
|
||||
|
||||
return func(network, host string, port uint16) (net.Conn, error) {
|
||||
if host == "" || port == 0 {
|
||||
return nil, errors.Errorf("invalid addr(%s:%d)", host, port)
|
||||
}
|
||||
|
||||
conn, err := dialFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := proxy.Wrap(conn, host, port); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ServeHTTP(ln net.Listener, r *router.Router) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Msg("serve socks5")
|
||||
}
|
||||
|
||||
go ServeHTTP(ln, r)
|
||||
start := time.Now()
|
||||
reread := reread.New(conn)
|
||||
defer reread.Close()
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(reread))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("read http request")
|
||||
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")
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
reread.Stop().Reread()
|
||||
err = relay.Relay(reread, rc)
|
||||
log.DebugWarn(err).
|
||||
Str("host", req.Host).
|
||||
Dur("spend", time.Since(start)).
|
||||
Msg("serve http")
|
||||
}
|
||||
|
||||
func ServeHTTPS(ln net.Listener, r *router.Router) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Msg("serve socks5")
|
||||
}
|
||||
|
||||
go ServeHTTPS(ln, r)
|
||||
start := time.Now()
|
||||
reread := reread.New(conn)
|
||||
defer reread.Close()
|
||||
|
||||
var domain string
|
||||
tls.Server(reread, &tls.Config{
|
||||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
domain = hello.ServerName
|
||||
return nil, nil
|
||||
},
|
||||
}).Handshake()
|
||||
|
||||
rc, err := r.ProxyDial("tcp", domain, 443)
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("host", domain).
|
||||
Msg("dial proxy")
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
reread.Stop().Reread()
|
||||
err = relay.Relay(reread, rc)
|
||||
log.DebugWarn(err).
|
||||
Str("host", domain).
|
||||
Dur("spend", time.Since(start)).
|
||||
Msg("serve http")
|
||||
}
|
||||
|
||||
func ServeSocks5(ln net.Listener, r *router.Router) {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Msg("serve socks5")
|
||||
}
|
||||
go ServeSocks5(ln, r)
|
||||
defer conn.Close()
|
||||
|
||||
reread := reread.New(conn)
|
||||
|
||||
byte1 := make([]byte, 1)
|
||||
if n, err := reread.Read(byte1); err != nil || n != 1 {
|
||||
log.Error().Err(err).Msg("read first byte")
|
||||
return
|
||||
}
|
||||
reread.Reread()
|
||||
|
||||
if byte1[0] == 5 {
|
||||
reread.Stop()
|
||||
if addr, err := socks5.New().Unwrap(reread); err != nil {
|
||||
log.Error().Err(err).Msg("read socks5 request")
|
||||
} else {
|
||||
host, port := addr.(*socks5.AddrHead).Addr()
|
||||
r.RouteHandle(reread, host, port)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(reread))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("read http request")
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/cristalhq/aconfig"
|
||||
"github.com/sower-proxy/conns/relay"
|
||||
"github.com/sower-proxy/conns/reread"
|
||||
"github.com/sower-proxy/deferlog"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/transport/sower"
|
||||
"github.com/wweir/sower/transport/trojan"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
var (
|
||||
version, date string
|
||||
|
||||
conf = struct {
|
||||
ServeIP string `usage:"listen to port 80 443 of this IP, eg: 0.0.0.0"`
|
||||
Password string `required:"true"`
|
||||
FakeSite string `default:"127.0.0.1:8080" usage:"fake site address or directoy"`
|
||||
|
||||
Cert struct {
|
||||
Email string
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
}{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := aconfig.LoaderFor(&conf, aconfig.Config{}).Load()
|
||||
log.InfoFatal(err).
|
||||
Str("version", version).
|
||||
Str("date", date).
|
||||
Interface("config", conf).
|
||||
Msg("Load config")
|
||||
}
|
||||
|
||||
func main() {
|
||||
cacheDir, _ := os.UserCacheDir()
|
||||
cacheDir = filepath.Join(cacheDir, "sower")
|
||||
if err := os.MkdirAll(cacheDir, 0o600); err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Str("dir", cacheDir).
|
||||
Msg("make cache dir")
|
||||
}
|
||||
|
||||
certManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Email: conf.Cert.Email,
|
||||
Cache: autocert.DirCache(cacheDir),
|
||||
}
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
GetCertificate: certManager.GetCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
tlsConf.GetCertificate = nil
|
||||
tlsConf.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
// 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")
|
||||
}()
|
||||
|
||||
ln, err := tls.Listen("tcp", net.JoinHostPort(conf.ServeIP, "443"), tlsConf)
|
||||
log.InfoFatal(err).
|
||||
Str("IP", conf.ServeIP).
|
||||
Msg("Start listen HTTPS service")
|
||||
|
||||
if si, err := os.Stat(conf.FakeSite); err == nil && si.IsDir() {
|
||||
http.NewFileTransport(http.Dir(conf.FakeSite))
|
||||
} else if _, _, err := net.SplitHostPort(conf.FakeSite); err == nil {
|
||||
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
|
||||
}
|
||||
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
go serve443(ln, fakeSite, sower, trojan)
|
||||
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)
|
||||
}()
|
||||
|
||||
// 1. detect if it's a sower underlaying connection
|
||||
reread.Reread()
|
||||
if addr, err = sower.Unwrap(reread); err == nil {
|
||||
reread.Stop()
|
||||
|
||||
dur, err = relay.RelayTo(reread, addr.String())
|
||||
return
|
||||
}
|
||||
|
||||
// 2. detect if it's a trojan underlaying connection
|
||||
reread.Reread()
|
||||
if addr, err = trojan.Unwrap(reread); err == nil {
|
||||
reread.Stop()
|
||||
|
||||
dur, err = relay.RelayTo(reread, addr.String())
|
||||
return
|
||||
}
|
||||
|
||||
// 3. fallback to fake site
|
||||
reread.Stop().Reread()
|
||||
dur, err = relay.RelayTo(reread, fakeSite)
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/fsnotify"
|
||||
)
|
||||
|
||||
var Conf = struct {
|
||||
ConfigFile string
|
||||
NetType string `toml:"net_type"`
|
||||
Password string `toml:"password"`
|
||||
|
||||
ServerPort string `toml:"server_port"`
|
||||
ServerAddr string `toml:"server_addr"`
|
||||
|
||||
DnsServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
ClientIPNet net.IP `toml:"-"`
|
||||
ClearDnsCache string `toml:"clear_dns_cache"`
|
||||
|
||||
BlockList []string `toml:"blocklist"`
|
||||
Suggestions []string `toml:"suggestions"`
|
||||
Verbose int `toml:"verbose"`
|
||||
}{}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", "", "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "QUIC", "proxy net type (QUIC|KCP|TCP)")
|
||||
flag.StringVar(&Conf.Password, "p", "12345678", "password")
|
||||
flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port")
|
||||
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)")
|
||||
flag.StringVar(&Conf.DnsServer, "d", "114.114.114.114", "client dns server")
|
||||
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
if Conf.ConfigFile == "" {
|
||||
return
|
||||
}
|
||||
if err := OnRefreash[0](); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
watchConfigFile()
|
||||
}
|
||||
|
||||
var OnRefreash = []func() error{func() error {
|
||||
if _, err := toml.DecodeFile(Conf.ConfigFile, &Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ClientIPNet = net.ParseIP(Conf.ClientIP)
|
||||
|
||||
// clear dns cache
|
||||
if Conf.ClearDnsCache != "" {
|
||||
ctx, _ := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
if err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDnsCache).Run(); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
// for glog
|
||||
if err := flag.Set("v", strconv.Itoa(Conf.Verbose)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
|
||||
func watchConfigFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
if err := watcher.Add(filepath.Dir(Conf.ConfigFile)); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if event.Op == fsnotify.Rename {
|
||||
if err := watcher.Add(Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
glog.Infof("watch %s event: %v", Conf.ConfigFile, event)
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
net_type="KCP"
|
||||
password="12345678"
|
||||
server_port="5533"
|
||||
server_addr="remote-server:5533" # replce it to remote server
|
||||
dns_server="114.114.114.114"
|
||||
# client_ip="192.168.0.1"
|
||||
# clear_dns_cache="sudo killall -HUP mDNSResponder"
|
||||
blocklist=[
|
||||
"*.google.com", # google
|
||||
"ocsp.pki.goog",
|
||||
"g.co",
|
||||
"*.goo.gl",
|
||||
"*.googleusercontent.com",
|
||||
"*.googleapis.com",
|
||||
"gmail.com",
|
||||
"*.youtube.com", # youtube
|
||||
"*.ytimg.com",
|
||||
"*.ggpht.com",
|
||||
"*.googlevideo.com",
|
||||
"*.vimeo.com",
|
||||
"*.facebook.com", # facebook
|
||||
"*.xx.fbcdn.net",
|
||||
"connect.facebook.net",
|
||||
"*.twitter.com", # twitter
|
||||
"t.co",
|
||||
"*.twimg.com",
|
||||
"www.linkedin.com",
|
||||
"www.reddit.com", # reddit
|
||||
"www.redditmedia.com",
|
||||
"*.blogspot.com", # blogspot
|
||||
"www.blogger.com",
|
||||
"www.feedburner.com",
|
||||
"img1.blogblog.com",
|
||||
"*.aws.amazon.com", # amazon
|
||||
"m.media-amazon.com",
|
||||
"*.awsstatic.com",
|
||||
"*.s3.amazonaws.com",
|
||||
"*.cloudfront.net", # atlassian
|
||||
"synchrony-cdn.atlassian.com",
|
||||
"avatar-cdn.atlassian.com",
|
||||
"*.medium.com",
|
||||
"accounts-static.cdn.mozilla.net", # firefox
|
||||
"*.services.mozilla.com",
|
||||
"pocket-image-cache.com", # pocket
|
||||
"*.golang.org", # golang
|
||||
"go.googlesource.com",
|
||||
"godoc.org",
|
||||
"gist.github.com", # github
|
||||
"*.k8s.io", # k8s
|
||||
"k8s.gcr.io",
|
||||
"*.wikipedia.org", # wikipeida
|
||||
"*.wikimedia.org",
|
||||
"*.wikisource.org",
|
||||
"lookup-api.apple.com",
|
||||
"lookup-api.apple.com.edgekey.net",
|
||||
"e16991.b.akamaiedge.net",
|
||||
"*.web.telegram.org", # telegram
|
||||
"core.telegram.org",
|
||||
"telegram.org",
|
||||
"telegram.me",
|
||||
"t.me",
|
||||
"*.slack.com", #slack
|
||||
"*.slack-edge.com",
|
||||
"js.driftt.com",
|
||||
"cloud.feedly.com", # feedly
|
||||
"static.grammarly.com", # grammarly
|
||||
"apkpure.com", # apkpure
|
||||
"download.apkpure.com",
|
||||
"*.nytimes.com", # New York times
|
||||
"*.nyt.com",
|
||||
"bandwagonhost.com", # bandwagonhost
|
||||
"www.bwh1.net",
|
||||
"*.akadns.net", # suggestions
|
||||
"*.haxx.se",
|
||||
"shadowsocks.org",
|
||||
"t66y.com",
|
||||
"ocsp.godaddy.com",
|
||||
"*.qualtrics.com",
|
||||
"*.starfieldtech.com",
|
||||
"*.typekit.net",
|
||||
"www.v2ray.com",
|
||||
]
|
||||
verbose=0
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
)
|
||||
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer string) {
|
||||
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// *Msg r has an TSIG record and it was validated
|
||||
if r.IsTsig() != nil && w.TsigStatus() == nil {
|
||||
r.SetTsig(r.Extra[len(r.Extra)-1].(*dns.TSIG).Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
|
||||
if len(r.Question) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
domain := r.Question[0].Name
|
||||
if idx := strings.IndexByte(domain, colon); idx > 0 {
|
||||
domain = domain[:idx]
|
||||
}
|
||||
|
||||
if len(conf.Conf.BlockList) == 0 {
|
||||
bestTry(w, r, domain, dnsServer)
|
||||
} else {
|
||||
manual(w, r, domain, dnsServer)
|
||||
}
|
||||
})
|
||||
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func bestTry(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
msg, _ := dns.Exchange(r, dnsServer+":53")
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
if len(msg.Answer) == 0 { // expose any response
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var ip string
|
||||
switch msg.Answer[0].(type) {
|
||||
case *dns.A:
|
||||
ip = msg.Answer[0].(*dns.A).A.String()
|
||||
case *dns.AAAA:
|
||||
ip = "[" + msg.Answer[0].(*dns.AAAA).AAAA.String() + "]"
|
||||
default:
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := net.DialTimeout("tcp", ip+":http", time.Second); err != nil {
|
||||
glog.V(2).Infoln(ip+":80", err)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
return
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
func manual(w dns.ResponseWriter, r *dns.Msg, domain, dnsServer string) {
|
||||
if rule.Match(strings.TrimSuffix(domain, ".")) {
|
||||
glog.V(2).Infof("match %s suss", domain)
|
||||
w.WriteMsg(localA(r, domain))
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := dns.Exchange(r, dnsServer+":53")
|
||||
if msg == nil { // expose any response except nil
|
||||
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
|
||||
return
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
|
||||
if conf.Conf.Verbose != 0 && len(msg.Answer) != 0 {
|
||||
go func() {
|
||||
_, err := net.DialTimeout("tcp", domain+":http", 2*time.Second)
|
||||
if err == nil || !strings.Contains(err.Error(), "timeout") {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = net.DialTimeout("tcp", domain+":https", 3*time.Second)
|
||||
if err == nil || !strings.Contains(err.Error(), "timeout") {
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(1).Infof("SUGGEST check (%s) http(s) service: %s", domain, err)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func localA(r *dns.Msg, domain string) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 20},
|
||||
A: conf.Conf.ClientIPNet,
|
||||
}}
|
||||
return m
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Node map[string]*Node
|
||||
}
|
||||
|
||||
func NewNode() *Node {
|
||||
return &Node{Node: map[string]*Node{}}
|
||||
}
|
||||
func NewNodeFromRule(rules ...string) *Node {
|
||||
node := NewNode()
|
||||
for i := range rules {
|
||||
node.Add(strings.Split(rules[i], "."))
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return n.string("")
|
||||
}
|
||||
func (n *Node) string(prefix string) (out string) {
|
||||
for key, val := range n.Node {
|
||||
out += prefix + key + "\n" + val.string(prefix+" ")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Node) Add(secs []string) {
|
||||
length := len(secs)
|
||||
switch length {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
n.Node[secs[length-1]] = NewNode()
|
||||
default:
|
||||
subNode, ok := n.Node[secs[length-1]]
|
||||
if !ok {
|
||||
subNode = NewNode()
|
||||
n.Node[secs[length-1]] = subNode
|
||||
}
|
||||
subNode.Add(secs[:length-1])
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) Match(addr string) bool {
|
||||
return n.matchSecs(strings.Split(addr, "."))
|
||||
}
|
||||
|
||||
func (n *Node) matchSecs(secs []string) bool {
|
||||
length := len(secs)
|
||||
if length == 0 {
|
||||
switch len(n.Node) {
|
||||
case 0:
|
||||
return true
|
||||
case 1:
|
||||
_, ok := n.Node["*"]
|
||||
return ok
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if n, ok := n.Node[secs[length-1]]; ok {
|
||||
return n.matchSecs(secs[:length-1])
|
||||
}
|
||||
|
||||
_, ok := n.Node["*"]
|
||||
return ok
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/conf"
|
||||
)
|
||||
|
||||
var rule *Node
|
||||
|
||||
func init() {
|
||||
rule = NewNodeFromRule(conf.Conf.BlockList...)
|
||||
glog.V(2).Infof("block rule:\n%s", rule)
|
||||
|
||||
conf.OnRefreash = append(conf.OnRefreash, func() error {
|
||||
rule = NewNodeFromRule(conf.Conf.BlockList...)
|
||||
glog.V(2).Infof("block rule:\n%s", rule)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,28 +1,36 @@
|
||||
module github.com/wweir/sower
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/golang/mock v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.7.0 // indirect
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
|
||||
github.com/lucas-clemente/quic-go v0.10.0
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
||||
github.com/miekg/dns v1.1.1
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
||||
github.com/tjfoc/gmsm v1.0.1 // indirect
|
||||
github.com/wweir/fsnotify v1.4.8
|
||||
github.com/xtaci/kcp-go v4.3.2+incompatible
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc // indirect
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
|
||||
github.com/cristalhq/aconfig v0.18.6
|
||||
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.4.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/oschwald/geoip2-golang v1.11.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sower-proxy/conns v0.1.1
|
||||
github.com/sower-proxy/deferlog v1.0.7
|
||||
github.com/sower-proxy/mem v0.0.3
|
||||
golang.org/x/crypto v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||
github.com/rs/zerolog v1.32.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,68 +1,70 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
|
||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE=
|
||||
github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
|
||||
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
|
||||
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
||||
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
|
||||
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/wweir/fsnotify v1.4.8 h1:zxOoqwE5p91F3rmcYLQheoSeTlIVqwi2b5T7UlpElWE=
|
||||
github.com/wweir/fsnotify v1.4.8/go.mod h1:4GuxIc23DXfJI1/LhBXG5eE/wJ4CzL+kk5M7woxVprc=
|
||||
github.com/xtaci/kcp-go v4.3.2+incompatible h1:vM5gqHI8p5xa3eXQH279+16fG86gCz/IRamaQvWG+a8=
|
||||
github.com/xtaci/kcp-go v4.3.2+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.6 h1:8KRBznzdjUUiaa7HeIpYbMx1uPE1/xOBEU1ajsnmNME=
|
||||
github.com/cristalhq/aconfig v0.18.6/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/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.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
|
||||
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
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.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
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 v1.0.7 h1:2gRkdTeSHPhnAj6uwBJ74ufqEmn1spT5o1Y3jGXY9zU=
|
||||
github.com/sower-proxy/deferlog v1.0.7/go.mod h1:TVXk/Pm7TAs1ZqayZfrxSbwCl+SSm7+p1xcEPkKHWvQ=
|
||||
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conf := conf.Conf
|
||||
glog.Infoln("Starting:", conf)
|
||||
|
||||
if conf.ServerAddr == "" {
|
||||
proxy.StartServer(conf.NetType, conf.ServerPort, conf.Password)
|
||||
} else {
|
||||
go dns.StartDNS(conf.DnsServer)
|
||||
proxy.StartClient(conf.NetType, conf.ServerAddr, conf.Password)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ParseAddr(conn net.Conn) (teeConn *TeeConn, addr string, err error) {
|
||||
teeConn = &TeeConn{Conn: conn}
|
||||
teeConn.StartOrReset()
|
||||
defer teeConn.Stop()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if n, err := teeConn.Read(buf); err != nil {
|
||||
return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n])
|
||||
}
|
||||
|
||||
teeConn.StartOrReset()
|
||||
|
||||
// https
|
||||
if buf[0] == 0x16 { // SSL handleshake
|
||||
host, _, err := extractSNI(io.Reader(teeConn))
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
return teeConn, host, nil
|
||||
}
|
||||
return teeConn, host + ":443", nil
|
||||
}
|
||||
|
||||
// http
|
||||
b := bufio.NewReader(teeConn)
|
||||
resp, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return teeConn, "", err
|
||||
}
|
||||
if strings.Contains(resp.Host, ":") {
|
||||
return teeConn, resp.Host, nil
|
||||
}
|
||||
return teeConn, resp.Host + ":80", nil
|
||||
}
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func extractSNI(r io.Reader) (string, int, error) {
|
||||
handshake, tlsver, err := handshakeRecord(r)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading TLS record: %s", err)
|
||||
}
|
||||
|
||||
sni, err := parseHello(handshake)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
if len(sni) == 0 {
|
||||
// ClientHello did not present an SNI extension. Valid packet,
|
||||
// no hostname.
|
||||
return "", tlsver, nil
|
||||
}
|
||||
|
||||
hostname, err := parseSNI(sni)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("parsing SNI extension: %s", err)
|
||||
}
|
||||
return hostname, tlsver, nil
|
||||
}
|
||||
|
||||
// Extract the indicated hostname, if any, from the given SNI
|
||||
// extension bytes.
|
||||
func parseSNI(b []byte) (string, error) {
|
||||
b, _, err := vector(b, 2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var ret []byte
|
||||
for len(b) >= 3 {
|
||||
typ := b[0]
|
||||
ret, b, err = vector(b[1:], 2)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("truncated SNI extension")
|
||||
}
|
||||
|
||||
if typ == sniHostnameID {
|
||||
return string(ret), nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return "", fmt.Errorf("trailing garbage at end of SNI extension")
|
||||
}
|
||||
|
||||
// No DNS-based SNI present.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
const sniExtensionID = 0
|
||||
const sniHostnameID = 0
|
||||
|
||||
// Parse a TLS handshake record as a ClientHello message and extract
|
||||
// the SNI extension bytes, if any.
|
||||
func parseHello(b []byte) ([]byte, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("zero length handshake record")
|
||||
}
|
||||
if b[0] != 1 {
|
||||
return nil, fmt.Errorf("non-ClientHello handshake record type %d", b[0])
|
||||
}
|
||||
|
||||
// We're expecting a stricter TLS parser to run after we've
|
||||
// proxied, so we ignore any trailing bytes that might be present
|
||||
// (e.g. another handshake message).
|
||||
b, _, err := vector(b[1:], 3)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello: %s", err)
|
||||
}
|
||||
|
||||
// ClientHello must be at least 34 bytes to reach the first vector
|
||||
// length byte. The actual minimal size is larger than that, but
|
||||
// vector() will correctly handle truncated packets.
|
||||
if len(b) < 34 {
|
||||
return nil, errors.New("ClientHello packet too short")
|
||||
}
|
||||
|
||||
if b[0] != 3 {
|
||||
return nil, fmt.Errorf("ClientHello has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
switch b[1] {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, fmt.Errorf("TLS record has unsupported version %d.%d", b[0], b[1])
|
||||
}
|
||||
|
||||
// Skip over version and random struct
|
||||
b = b[34:]
|
||||
|
||||
// We don't technically care about SessionID, but we care that the
|
||||
// framing is well-formed all the way up to the SNI field, so that
|
||||
// we are sure that we're pulling the same SNI bytes as the
|
||||
// eventual TLS implementation.
|
||||
vec, b, err := vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello SessionID: %s", err)
|
||||
}
|
||||
if len(vec) > 32 {
|
||||
return nil, fmt.Errorf("ClientHello SessionID too long (%db)", len(vec))
|
||||
}
|
||||
|
||||
// Likewise, we're just checking the bare minimum of framing.
|
||||
vec, b, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CipherSuites: %s", err)
|
||||
}
|
||||
if len(vec) < 2 || len(vec)%2 != 0 {
|
||||
return nil, fmt.Errorf("ClientHello CipherSuites invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
vec, b, err = vector(b, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello CompressionMethods: %s", err)
|
||||
}
|
||||
if len(vec) < 1 {
|
||||
return nil, fmt.Errorf("ClientHello CompressionMethods invalid length %d", len(vec))
|
||||
}
|
||||
|
||||
// Finally, we reach the extensions.
|
||||
if len(b) == 0 {
|
||||
// No extensions. This is not an error, it just means we have
|
||||
// no SNI payload.
|
||||
return nil, nil
|
||||
}
|
||||
b, vec, err = vector(b, 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extensions: %s", err)
|
||||
}
|
||||
if len(vec) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(vec))
|
||||
}
|
||||
|
||||
for len(b) >= 4 {
|
||||
typ := binary.BigEndian.Uint16(b[:2])
|
||||
vec, b, err = vector(b[2:], 2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading ClientHello extension %d: %s", typ, err)
|
||||
}
|
||||
if typ == sniExtensionID {
|
||||
// Found the SNI extension, return its payload. We don't
|
||||
// care about anything in the packet beyond this point.
|
||||
return vec, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
return nil, fmt.Errorf("%d bytes of trailing garbage in ClientHello", len(b))
|
||||
}
|
||||
|
||||
// Successfully parsed all extensions, but there was no SNI.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const maxTLSRecordLength = 16384
|
||||
|
||||
// Read one TLS record, which must be for the handshake protocol, from r.
|
||||
func handshakeRecord(r io.Reader) ([]byte, int, error) {
|
||||
var hdr struct {
|
||||
Type uint8
|
||||
Major, Minor uint8
|
||||
Length uint16
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &hdr); err != nil {
|
||||
return nil, 0, fmt.Errorf("reading TLS record header: %s", err)
|
||||
}
|
||||
|
||||
if hdr.Type != 22 {
|
||||
return nil, 0, fmt.Errorf("TLS record is not a handshake")
|
||||
}
|
||||
|
||||
if hdr.Major != 3 {
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
switch hdr.Minor {
|
||||
case 1, 2, 3:
|
||||
// TLS 1.0, TLS 1.1, TLS 1.2
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("TLS record has unsupported version %d.%d", hdr.Major, hdr.Minor)
|
||||
}
|
||||
|
||||
if hdr.Length > maxTLSRecordLength {
|
||||
return nil, 0, fmt.Errorf("TLS record length is greater than %d", maxTLSRecordLength)
|
||||
}
|
||||
|
||||
ret := make([]byte, hdr.Length)
|
||||
if _, err := io.ReadFull(r, ret); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return ret, int(hdr.Minor), nil
|
||||
}
|
||||
|
||||
func vector(b []byte, lenBytes int) ([]byte, []byte, error) {
|
||||
if len(b) < lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
var l int
|
||||
for _, b := range b[:lenBytes] {
|
||||
l = (l << 8) + int(b)
|
||||
}
|
||||
if len(b) < l+lenBytes {
|
||||
return nil, nil, errors.New("not enough space in packet for vector")
|
||||
}
|
||||
return b[lenBytes : l+lenBytes], b[l+lenBytes:], nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type TeeConn struct {
|
||||
net.Conn
|
||||
buf []byte
|
||||
offset int
|
||||
tee bool // read
|
||||
}
|
||||
|
||||
func (t *TeeConn) StartOrReset() {
|
||||
t.offset = 0
|
||||
t.tee = true
|
||||
}
|
||||
func (t *TeeConn) Stop() {
|
||||
t.offset = 0
|
||||
t.tee = false
|
||||
}
|
||||
|
||||
func (t *TeeConn) 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, err = t.Conn.Read(b)
|
||||
if t.tee {
|
||||
t.buf = append(t.buf, b[:n]...)
|
||||
t.offset += n
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/krolaw/dhcp4"
|
||||
"github.com/libp2p/go-reuseport"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var xid = make([]byte, 4)
|
||||
var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67")
|
||||
|
||||
func GetDNSServer() ([]string, error) {
|
||||
iface, err := PickInternetInterface()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pick interface")
|
||||
}
|
||||
|
||||
rand.Read(xid)
|
||||
pack := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, net.IPv4(0, 0, 0, 0), xid, true, []dhcp4.Option{
|
||||
{Code: dhcp4.OptionRequestedIPAddress, Value: []byte(iface.IP.To4())},
|
||||
{Code: dhcp4.End},
|
||||
})
|
||||
|
||||
var conn net.PacketConn
|
||||
if runtime.GOOS == "windows" {
|
||||
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
|
||||
return nil, errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
} else {
|
||||
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
|
||||
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 nil, errors.Wrap(err, "write broadcast")
|
||||
}
|
||||
|
||||
buf := make([]byte, 1500 /*MTU*/)
|
||||
n, _, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read dhcp offer")
|
||||
}
|
||||
|
||||
pack = dhcp4.Packet(buf[:n])
|
||||
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
|
||||
if len(dnsBytes) < 4 || len(dnsBytes)%4 != 0 {
|
||||
return nil, errors.New("invalid DNS setting in upstream network device")
|
||||
}
|
||||
|
||||
ips := []string{}
|
||||
for i := 0; i < len(dnsBytes); i += 4 {
|
||||
ips = append(ips, net.IP(dnsBytes[i:i+4]).String())
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/pkg/dhcp"
|
||||
)
|
||||
|
||||
func Example_dns() {
|
||||
got, err := dhcp.GetDNSServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// +build !windows
|
||||
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range ifaces {
|
||||
if len(ifaces[i].HardwareAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, _ := ifaces[i].Addrs()
|
||||
for _, addr := range addrs {
|
||||
if ip := addr.(*net.IPNet).IP.To4(); ip != nil {
|
||||
return &Iface{ifaces[i].HardwareAddr, ip}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dhcp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wweir/sower/pkg/dhcp"
|
||||
)
|
||||
|
||||
func Example_iface() {
|
||||
got, err := dhcp.PickInternetInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(got)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// +build windows
|
||||
|
||||
package dhcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PickInternetInterface pick the first active net interface
|
||||
func PickInternetInterface() (*Iface, error) {
|
||||
list, err := getAdapterList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
if list.DhcpEnabled == 1 && list.LeaseObtained != 0 {
|
||||
ip := bytes.TrimRight(list.IpAddressList.IpAddress.String[:], string([]byte{0}))
|
||||
return &Iface{net.HardwareAddr(list.Address[:list.AddressLength]), net.ParseIP(string(ip))}, nil
|
||||
}
|
||||
|
||||
if list.Next != nil {
|
||||
list = list.Next
|
||||
} else {
|
||||
return nil, errors.New("no valid interface")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAdapterList() (*syscall.IpAdapterInfo, error) {
|
||||
b := make([]byte, 1000)
|
||||
l := uint32(len(b))
|
||||
a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
// TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that
|
||||
// contains IPv4 address list only. We should use another API
|
||||
// for fetching IPv6 stuff from the kernel.
|
||||
err := syscall.GetAdaptersInfo(a, &l)
|
||||
if err == syscall.ERROR_BUFFER_OVERFLOW {
|
||||
b = make([]byte, l)
|
||||
a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
|
||||
err = syscall.GetAdaptersInfo(a, &l)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("GetAdaptersInfo", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dhcp
|
||||
|
||||
import "net"
|
||||
|
||||
// Iface is net interface address info
|
||||
type Iface struct {
|
||||
net.HardwareAddr
|
||||
net.IP
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package suffixtree_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/wweir/sower/pkg/suffixtree"
|
||||
)
|
||||
|
||||
func TestNode_Match(t *testing.T) {
|
||||
type test struct {
|
||||
arg string
|
||||
want bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
node *suffixtree.Node
|
||||
tests []test
|
||||
}{{
|
||||
"simple",
|
||||
suffixtree.NewNodeFromRules("a.wweir.cc", "b.wweir.cc"),
|
||||
[]test{
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"parent",
|
||||
suffixtree.NewNodeFromRules("wweir.cc", "a.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
},
|
||||
}, {
|
||||
"fuzz1",
|
||||
suffixtree.NewNodeFromRules("wweir.cc", "a.wweir.cc", "*.wweir.cc"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
{"a.b.wweir.cc", false},
|
||||
},
|
||||
}, {
|
||||
"fuzz2",
|
||||
suffixtree.NewNodeFromRules("a.*.cc", "c.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", false},
|
||||
{"c.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz3",
|
||||
suffixtree.NewNodeFromRules("*.*.cc", "iamp.*.*"),
|
||||
[]test{
|
||||
{"wweir.cc", false},
|
||||
{"a.wweir.cc", true},
|
||||
{"b.wweir.cc", true},
|
||||
{"iamp.wweir.cc", true},
|
||||
},
|
||||
}, {
|
||||
"fuzz4",
|
||||
suffixtree.NewNodeFromRules("**.cc", "a.**.com", "**.wweir.*"),
|
||||
[]test{
|
||||
{"wweir.cc", true},
|
||||
{"a.wweir.cc", true},
|
||||
{"a.b.wweir.cc", true},
|
||||
{"a.fuzz.com", true},
|
||||
{"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) {
|
||||
for _, test := range tt.tests {
|
||||
if got := tt.node.Match(test.arg); got != test.want {
|
||||
t.Errorf("Node.Match(%s) = %v, want %v", test.arg, got, test.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package suffixtree
|
||||
|
||||
func GCSlice[T any](arr []T) []T {
|
||||
old := arr
|
||||
return append(make([]T, 0, len(old)), old...)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/kcp"
|
||||
"github.com/wweir/sower/proxy/quic"
|
||||
"github.com/wweir/sower/proxy/tcp"
|
||||
"github.com/wweir/sower/shadow"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Dial(server string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func StartClient(netType, server, password string) {
|
||||
var connCh = listenLocal([]string{":80", ":443"})
|
||||
var client Client
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
client = quic.NewClient()
|
||||
case KCP.String():
|
||||
client = kcp.NewClient()
|
||||
case TCP.String():
|
||||
client = tcp.NewClient()
|
||||
}
|
||||
glog.Infoln("Client started.")
|
||||
|
||||
for {
|
||||
conn := <-connCh
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
|
||||
rc, err := client.Dial(server)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if rc, err = shadow.Shadow(rc, password); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
go relay(conn, rc)
|
||||
}
|
||||
}
|
||||
|
||||
func listenLocal(ports []string) <-chan net.Conn {
|
||||
connCh := make(chan net.Conn, 10)
|
||||
for i := range ports {
|
||||
go func(port string) {
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Errorln("accept", port, "fail:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}(ports[i])
|
||||
}
|
||||
|
||||
glog.Infoln("listening ports:", ports)
|
||||
return connCh
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
AckNodelay bool
|
||||
NoDelay int
|
||||
Interval int
|
||||
Resend int
|
||||
NoCongestion int
|
||||
SndWnd int
|
||||
RcvWnd int
|
||||
MTU int
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
NoDelay: 0,
|
||||
Interval: 50,
|
||||
Resend: 0,
|
||||
NoCongestion: 0,
|
||||
SndWnd: 0,
|
||||
RcvWnd: 0,
|
||||
MTU: 1350,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "dial")
|
||||
}
|
||||
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetWriteDelay(false)
|
||||
conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion)
|
||||
conn.SetWindowSize(c.SndWnd, c.RcvWnd)
|
||||
conn.SetMtu(c.MTU)
|
||||
conn.SetACKNoDelay(c.AckNodelay)
|
||||
|
||||
if err := conn.SetDSCP(c.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := conn.SetReadBuffer(c.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := conn.SetWriteBuffer(c.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
DataShard int
|
||||
ParityShard int
|
||||
DSCP int
|
||||
SockBuf int
|
||||
}
|
||||
|
||||
func NewServer(password string) *server {
|
||||
return &server{
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
SockBuf: 4194304,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ln.SetDSCP(s.DSCP); err != nil {
|
||||
return nil, errors.Wrap(err, "SetDSCP")
|
||||
}
|
||||
if err := ln.SetReadBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetReadBuffer")
|
||||
}
|
||||
if err := ln.SetWriteBuffer(s.SockBuf); err != nil {
|
||||
return nil, errors.Wrap(err, "SetWriteBuffer")
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.AcceptKCP()
|
||||
if err != nil {
|
||||
glog.Fatalln("KCP listen:", err)
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
|
||||
return connCh, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Code generated by "stringer -type=netType util.go"; DO NOT EDIT.
|
||||
|
||||
package proxy
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _netType_name = "QUICKCPTCP"
|
||||
|
||||
var _netType_index = [...]uint8{0, 4, 7, 10}
|
||||
|
||||
func (i netType) String() string {
|
||||
if i < 0 || i >= netType(len(_netType_index)-1) {
|
||||
return "netType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _netType_name[_netType_index[i]:_netType_index[i+1]]
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
conf *quic.Config
|
||||
sess quic.Session
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{
|
||||
// conf: &quic.Config{
|
||||
// HandshakeTimeout: 5 * time.Second,
|
||||
// MaxIncomingStreams: 1024,
|
||||
// KeepAlive: true,
|
||||
// },
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
if c.sess == nil {
|
||||
if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.conf); err != nil {
|
||||
return nil, errors.Wrap(err, "session")
|
||||
} else {
|
||||
c.sess = sess
|
||||
}
|
||||
}
|
||||
|
||||
var stream quic.Stream
|
||||
if err := WithTimeout(func() (err error) {
|
||||
if stream, err = c.sess.OpenStream(); err != nil {
|
||||
c.sess = nil
|
||||
}
|
||||
return
|
||||
}, time.Second); err != nil {
|
||||
return nil, errors.Wrap(err, "stream")
|
||||
}
|
||||
|
||||
return &streamConn{
|
||||
Stream: stream,
|
||||
sess: c.sess,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
conf *quic.Config
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{
|
||||
// conf: &quic.Config{
|
||||
// HandshakeTimeout: 5 * time.Second,
|
||||
// MaxIncomingStreams: 1024,
|
||||
// KeepAlive: true,
|
||||
// },
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := quic.ListenAddr(port, mockTlsPem(), s.conf)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
sess, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
go accept(sess, connCh)
|
||||
}
|
||||
}()
|
||||
return connCh, nil
|
||||
}
|
||||
|
||||
func accept(sess quic.Session, connCh chan<- net.Conn) {
|
||||
glog.V(1).Infoln("new session from ", sess.RemoteAddr())
|
||||
defer sess.Close()
|
||||
|
||||
for {
|
||||
stream, err := sess.AcceptStream()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
connCh <- &streamConn{stream, sess}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type streamConn struct {
|
||||
quic.Stream
|
||||
sess quic.Session
|
||||
}
|
||||
|
||||
func (s *streamConn) LocalAddr() net.Addr {
|
||||
return s.sess.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *streamConn) RemoteAddr() net.Addr {
|
||||
return s.sess.RemoteAddr()
|
||||
}
|
||||
|
||||
func mockTlsPem() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
}
|
||||
|
||||
func WithTimeout(fn func() error, timeout time.Duration) error {
|
||||
var okCh = make(chan struct{})
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
err = fn()
|
||||
close(okCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-okCh:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return errors.New("timeout: " + timeout.String())
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/parse"
|
||||
"github.com/wweir/sower/proxy/kcp"
|
||||
"github.com/wweir/sower/proxy/quic"
|
||||
"github.com/wweir/sower/proxy/tcp"
|
||||
"github.com/wweir/sower/shadow"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Listen(port string) (<-chan net.Conn, error)
|
||||
}
|
||||
|
||||
func StartServer(netType, port, password string) {
|
||||
var server Server
|
||||
switch netType {
|
||||
case QUIC.String():
|
||||
server = quic.NewServer()
|
||||
case KCP.String():
|
||||
server = kcp.NewServer(password)
|
||||
case TCP.String():
|
||||
server = tcp.NewServer()
|
||||
}
|
||||
|
||||
if port == "" {
|
||||
glog.Fatalln("port must set")
|
||||
}
|
||||
if !strings.Contains(port, ":") {
|
||||
port = ":" + port
|
||||
}
|
||||
connCh, err := server.Listen(port)
|
||||
if err != nil {
|
||||
glog.Fatalf("listen %v fail: %s", port, err)
|
||||
}
|
||||
glog.Infoln("Server started.")
|
||||
|
||||
for {
|
||||
conn := <-connCh
|
||||
conn, err := shadow.Shadow(conn, password)
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
go handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handle(conn net.Conn) {
|
||||
conn, addr, err := parse.ParseAddr(conn)
|
||||
if err != nil {
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr)
|
||||
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
glog.Warningln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := rc.(*net.TCPConn).SetKeepAlive(true); err != nil {
|
||||
glog.Warningln(err)
|
||||
}
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import "net"
|
||||
|
||||
type client struct {
|
||||
}
|
||||
|
||||
func NewClient() *client {
|
||||
return &client{}
|
||||
}
|
||||
|
||||
func (c *client) Dial(server string) (net.Conn, error) {
|
||||
return net.Dial("tcp", server)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
}
|
||||
|
||||
func NewServer() *server {
|
||||
return &server{}
|
||||
}
|
||||
|
||||
func (s *server) Listen(port string) (<-chan net.Conn, error) {
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connCh := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.Fatalln("TCP listen:", err)
|
||||
}
|
||||
|
||||
connCh <- conn
|
||||
}
|
||||
}()
|
||||
return connCh, nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=netType $GOFILE
|
||||
type netType int
|
||||
|
||||
const (
|
||||
QUIC netType = iota
|
||||
KCP
|
||||
TCP
|
||||
)
|
||||
|
||||
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) {
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
glog.V(1).Infof("%s<>%s -> %s<>%s: %s", src.RemoteAddr(), src.LocalAddr(), dst.LocalAddr(), dst.RemoteAddr(), err)
|
||||
}
|
||||
|
||||
if atomic.CompareAndSwapInt32(exitFlag, 0, 1) {
|
||||
now := time.Now()
|
||||
src.SetReadDeadline(now)
|
||||
dst.SetWriteDeadline(now)
|
||||
} else {
|
||||
src.Close()
|
||||
dst.Close()
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
)
|
||||
|
||||
func (r *Router) localSite(domain string) bool {
|
||||
// parse domain to IP
|
||||
ip := net.ParseIP(domain)
|
||||
if ip == nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
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")
|
||||
return false
|
||||
}
|
||||
|
||||
ip = ips[0]
|
||||
}
|
||||
|
||||
// CIDR match
|
||||
for _, cidr := range r.country.cidrs {
|
||||
if cidr.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MMDB match CN
|
||||
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")
|
||||
return false
|
||||
}
|
||||
|
||||
if city.Country.IsoCode == "CN" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/pkg/dhcp"
|
||||
)
|
||||
|
||||
func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
|
||||
// https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071
|
||||
if len(req.Question) == 0 {
|
||||
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeFormatError))
|
||||
return
|
||||
}
|
||||
|
||||
domain := req.Question[0].Name
|
||||
// 1. rule_based( block > direct > proxy )
|
||||
switch {
|
||||
case r.BlockRule.Match(domain):
|
||||
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeNameError))
|
||||
return
|
||||
|
||||
case r.DirectRule.Match(domain):
|
||||
|
||||
case r.ProxyRule.Match(domain):
|
||||
host, _, err := net.SplitHostPort(w.LocalAddr().String())
|
||||
log.DebugWarn(err).Str("host", host).Msg("proxy dns")
|
||||
_ = w.WriteMsg(r.dnsProxyA(domain, net.ParseIP(host), req))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. direct with cache, do not fallback to proxy to avoid side-effect
|
||||
resp, err := r.Exchange(req)
|
||||
if err != nil {
|
||||
_ = w.WriteMsg(r.dnsFail(req, dns.RcodeServerFailure))
|
||||
} else {
|
||||
_ = w.WriteMsg(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) dnsFail(req *dns.Msg, rcode int) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(req, rcode)
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *Router) dnsProxyA(domain string, localIP net.IP, req *dns.Msg) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
|
||||
if localIP.To4() != nil {
|
||||
m.Answer = []dns.RR{&dns.A{
|
||||
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},
|
||||
AAAA: localIP,
|
||||
}}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
var (
|
||||
upstreamAddrs []string
|
||||
upstreamIndex = -1
|
||||
queryCount = 0
|
||||
)
|
||||
|
||||
func (r *Router) Exchange(req *dns.Msg) (_ *dns.Msg, err error) {
|
||||
queryCount++
|
||||
if upstreamIndex < 0 {
|
||||
dnsIPs, err := dhcp.GetDNSServer()
|
||||
log.Err(err).
|
||||
Int("queryCount", queryCount).
|
||||
Strs("dns", dnsIPs).
|
||||
Msg("get dns server")
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
queryCount = 0
|
||||
upstreamAddrs = addrs
|
||||
upstreamIndex = len(addrs) - 1
|
||||
log.Info().Str("ip", upstreamAddrs[upstreamIndex]).Msg("use upstream dns")
|
||||
}
|
||||
|
||||
resp, err := dns.Exchange(req, upstreamAddrs[upstreamIndex])
|
||||
if err != nil {
|
||||
upstreamIndex--
|
||||
if upstreamIndex >= 0 {
|
||||
log.Info().Str("ip", upstreamAddrs[upstreamIndex]).Msg("use upstream dns")
|
||||
resp, err = dns.Exchange(req, upstreamAddrs[upstreamIndex])
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/sower-proxy/mem"
|
||||
)
|
||||
|
||||
var accessCache = mem.NewRotateCache(time.Hour, httpPing)
|
||||
|
||||
func (r *Router) isAccess(domain string, port uint16) bool {
|
||||
switch port {
|
||||
case 80:
|
||||
case 443:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
ok, _ := accessCache.Get(domain)
|
||||
return ok
|
||||
}
|
||||
|
||||
var pingClient = http.Client{
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
|
||||
func httpPing(key string) (bool, error) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
var err80, err443 error
|
||||
go func() {
|
||||
_, err80 = pingClient.Head("http://" + key)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
_, err443 = pingClient.Head("https://" + key)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
if err80 != nil && err443 != nil {
|
||||
log.Warn().
|
||||
Errs("errs", []error{err80, err443}).
|
||||
Msg("Failed to ping")
|
||||
}
|
||||
|
||||
return err80 == nil && err443 == nil, nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
geoip2 "github.com/oschwald/geoip2-golang"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sower-proxy/conns/relay"
|
||||
"github.com/sower-proxy/deferlog"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"github.com/wweir/sower/pkg/suffixtree"
|
||||
)
|
||||
|
||||
type ProxyDialFn func(network, host string, port uint16) (net.Conn, error)
|
||||
type Router struct {
|
||||
BlockRule *suffixtree.Node
|
||||
DirectRule *suffixtree.Node
|
||||
ProxyRule *suffixtree.Node
|
||||
ProxyDial ProxyDialFn
|
||||
|
||||
dns struct {
|
||||
dns.Client
|
||||
fallbackDNS string
|
||||
serveIP net.IP
|
||||
}
|
||||
|
||||
country struct {
|
||||
*geoip2.Reader
|
||||
cidrs []*net.IPNet
|
||||
}
|
||||
}
|
||||
|
||||
func NewRouter(serveIP, fallbackDNS, mmdbFile string, proxyDial ProxyDialFn) *Router {
|
||||
r := Router{
|
||||
ProxyDial: proxyDial,
|
||||
}
|
||||
|
||||
r.dns.serveIP = net.ParseIP(serveIP)
|
||||
r.dns.fallbackDNS = fallbackDNS
|
||||
|
||||
var err error
|
||||
r.country.Reader, err = geoip2.Open(mmdbFile)
|
||||
log.InfoWarn(err).Str("file", mmdbFile).Msg("open geoip2 db")
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
r.country.cidrs = append(r.country.cidrs, ipnet)
|
||||
}
|
||||
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")
|
||||
}()
|
||||
|
||||
addr := net.JoinHostPort(domain, strconv.FormatUint(uint64(port), 10))
|
||||
|
||||
// 1. rule_based( block > direct > proxy )
|
||||
// 2. detect_based( CN IP || access site )
|
||||
// 3. fallback( proxy )
|
||||
switch {
|
||||
case r.BlockRule.Match(domain):
|
||||
return nil
|
||||
|
||||
case r.DirectRule.Match(domain):
|
||||
return r.DirectHandle(conn, addr)
|
||||
|
||||
case r.ProxyRule.Match(domain):
|
||||
return r.ProxyHandle(conn, domain, port)
|
||||
|
||||
case r.localSite(domain), r.isAccess(domain, port):
|
||||
return r.DirectHandle(conn, addr)
|
||||
default:
|
||||
return r.ProxyHandle(conn, domain, port)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) ProxyHandle(conn net.Conn, domain string, port uint16) error {
|
||||
start := time.Now()
|
||||
rc, err := r.ProxyDial("tcp", domain, port)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "proxy dial (%s:%d), spend (%s)", domain, port, time.Since(start))
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_ = relay.Relay(conn, rc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) DirectHandle(conn net.Conn, addr string) error {
|
||||
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
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// transter conn to be a crypto conn
|
||||
// stream mode, blocksize is 1350, for mtu
|
||||
// data [0 ... 1332]
|
||||
// block [size][2 ... 1350]
|
||||
// payload size + data size + aead overhead => (2+1332+16)
|
||||
package shadow
|
||||
@@ -1,119 +0,0 @@
|
||||
package shadow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const MAX_SIZE = 0xFFFF
|
||||
|
||||
type conn struct {
|
||||
maxSize int
|
||||
aead cipher.AEAD
|
||||
encryptNonce func() []byte
|
||||
decryptNonce func() []byte
|
||||
writeBuf []byte
|
||||
readBuf []byte
|
||||
readOffset int
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
// read from buffer
|
||||
if c.readOffset != 0 {
|
||||
dataSize := len(c.readBuf) - c.aead.Overhead()
|
||||
n = copy(b, c.readBuf[c.readOffset:dataSize])
|
||||
c.readOffset += n
|
||||
|
||||
if c.readOffset == dataSize {
|
||||
c.readOffset = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// read from conn
|
||||
dataSize := 0
|
||||
{ //read data size
|
||||
c.readBuf = make([]byte, 2+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
dataSize = int(c.readBuf[0])<<8 + int(c.readBuf[1])
|
||||
}
|
||||
{ // read data
|
||||
c.readBuf = make([]byte, dataSize+c.aead.Overhead())
|
||||
if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// buffer extra data
|
||||
if n = copy(b, c.readBuf[:dataSize]); n < dataSize {
|
||||
c.readOffset = n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
bLen := len(b)
|
||||
dataSize := MAX_SIZE - (2 + c.aead.Overhead()) - c.aead.Overhead()
|
||||
if bLen < c.maxSize {
|
||||
dataSize = bLen
|
||||
}
|
||||
|
||||
// BigEndian
|
||||
c.writeBuf[0], c.writeBuf[1] = byte((dataSize&0xFF00)>>8), byte(dataSize&0xFF)
|
||||
|
||||
c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:2], nil)
|
||||
c.aead.Seal(c.writeBuf[:2+c.aead.Overhead()], c.encryptNonce(), b[:dataSize], nil)
|
||||
|
||||
_, err = c.Conn.Write(c.writeBuf[:dataSize+(2+c.aead.Overhead())+c.aead.Overhead()])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func Shadow(c net.Conn, password string) (net.Conn, error) {
|
||||
block, err := aes.NewCipher([]byte(password + password)[:16])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "password too short")
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GCM")
|
||||
}
|
||||
|
||||
return &conn{
|
||||
maxSize: MAX_SIZE - (2 - aead.Overhead()) - aead.Overhead(),
|
||||
aead: aead,
|
||||
encryptNonce: newNonce(password, aead.NonceSize()),
|
||||
decryptNonce: newNonce(password, aead.NonceSize()),
|
||||
writeBuf: make([]byte, 0xFFFF),
|
||||
Conn: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newNonce(password string, size int) func() []byte {
|
||||
num, _ := binary.Varint([]byte(password))
|
||||
rnd := rand.New(rand.NewSource(num))
|
||||
|
||||
buf := make([]byte, size)
|
||||
return func() []byte {
|
||||
rnd.Read(buf)
|
||||
return buf
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="351px" height="491px" viewBox="-0.5 -0.5 351 491" content="<mxfile><diagram id="u2y3ysnPqCVZKoHCRF4D" name="第 1 页">3VlNc9owEP01HJPxB/7gGCBtD2mbGdppcxRY2GpsyyOLAv31XSEZW5ZJKHEIzSWR1itZem/1dmUG7iTbfGSoSD7TCKcDx4o2A3c6cJyRH8JfYdhKQxAE0hAzEkmTXRtm5A9WRktZVyTCpebIKU05KXTjguY5XnDNhhija91tSVP9rQWKsWGYLVBqWn+QiCfSGjpBbf+ESZxUb7b9kXySocpZ7aRMUETXDZN7O3AnjFIuW9lmglOBXYWLHPfhwNP9whjO+TEDxDMx4jdKV2pzamF8W+2W0VUeYTHAGrjjdUI4nhVoIZ6ugV6wJTxLoWdDU02HGcebg2uy9zuFCME0w5xtwUUNcDwFjooOO1D9dY21W/kkDZyHI2VEit94P3cNATQUCt2IDA1APn37dj8zUMERhITqUsYTGtMcpbe1dazjVvvcUVootH5hzrcqvtGKUx1LwIttf4rx117VfWg+m27U5LK3Vb0DgMPy6Yot1PoVzxyxGCsvV5rEzp7kieEUcfJbPwwvwdw2QS/p4rH0wLYqSs4wyt6EgA3hEn/HU92HCnFo1/CLzino9we1GnpPCby1PkqhfpRcv3VCJP1qVE3YDWNo23ArhEN5+D2u1/2emn85Yx0N+z0eFyD+29If/J/0e/556N8L72vRHxj6EBEmMjvYSEl4Z3TcoTmUHRqjKCVxDu0FkIMZGESiIpDYb9SDjESRDB5ckj9ovptP0KoggMm98cCbdhPtH8x+qhJRE2rJvisFWteW7QcapldqplNDonKhy2WJeYuefyXErBpKugZAHT8FLMZz0Yr5DqS2ha1SKN1gPXkkKkDMJY1zVGJhgEMrmOmhBuk4ggYxh2uQlnAObbMG2fs0axDb6qEGscO3kLujlKv3GuGAojg6/k4b1gPKdYq4jN5XbjkXQ6OzMeR2q010cSrhDI9UibAHkQguViNUGmxeLV6n4PnXOsWzPI0vz23djp9RoZb/i+uasCOw5bXHR5kI13xein+wKBhIYrxbXMHoZnsZFU9Vl7244Lmyrl3bt/WKZ/iyiqdaUGtELwWQbzA3Fp+VdiWQRh04fQdzafBVJqgQzVWW3iw4bVKzo/GeQl1LqKBoTjmnWQd3nLY0DYqnlOR4sv/u9WTWOF7o2mK/v1g0P8l06Fz7vnFSMWR+kTprfj4+PYvOPWYEtiiO2vEpe3QuwXxe4ZxX+kDgXpby2mZNMf0yq+4fgotE/M2FUFjLlK7Nc30J+ludjV4EeBiEoQb6Jd04RwZfX4Ei9j7k1W1H+xnltXp1A9klehTBDwBhA1fYEX+9CBc6XEpJFlOXnNFHgDsV/E1zmuOe0lnru5gdmnj7HXi3xfEkvG0Db6X77xlxN/SeRTzoB3Ho1r+fSaWpf4R0b/8C</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="0" y="0" width="350" height="490" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<path d="M 170 240 L 170 156.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 170 151.12 L 173.5 158.12 L 170 156.37 L 166.5 158.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 195px; margin-left: 170px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
HTTPS
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="170" y="198" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
HTTPS
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 100 240 L 100 190 L 36.37 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 31.12 190 L 38.12 186.5 L 36.37 190 L 38.12 193.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 190px; margin-left: 90px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
socks5 upstream
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="90" y="193" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
socks5 upstream
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 240 240 L 240 190 L 303.63 190" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 308.88 190 L 301.88 193.5 L 303.63 190 L 301.88 186.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 192px; margin-left: 252px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
direct visit
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="252" y="195" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
direct visit
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="30" y="240" width="280" height="100" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 278px; height: 1px; padding-top: 290px; margin-left: 31px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
sower
|
||||
<br/>
|
||||
<br/>
|
||||
rules and detect based router
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="170" y="294" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
sower...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 170 70 L 170 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 170 31.12 L 173.5 38.12 L 170 36.37 L 166.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 240 70 L 240 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 240 31.12 L 243.5 38.12 L 240 36.37 L 236.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="30" y="70" width="280" height="80" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 278px; height: 1px; padding-top: 110px; margin-left: 31px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
sowerd
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="170" y="114" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
sowerd
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 255 390 L 255 360 L 170 360 L 170 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 170 341.12 L 173.5 348.12 L 170 346.37 L 166.5 348.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 357px; margin-left: 251px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
socks5 intelligent proxy
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="251" y="360" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
socks5 intelligent proxy
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<ellipse cx="255" cy="397.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<path d="M 255 405 L 255 430 M 255 410 L 240 410 M 255 410 L 270 410 M 255 430 L 240 450 M 255 430 L 270 450" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 457px; margin-left: 255px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
Browser Users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="255" y="469" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Brows...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 85 390 L 85 360 L 170 360 L 170 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 170 341.12 L 173.5 348.12 L 170 346.37 L 166.5 348.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 360px; margin-left: 91px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
DNS route the net flow
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="91" y="363" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
DNS route the net flow
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<ellipse cx="85" cy="397.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<path d="M 85 405 L 85 430 M 85 410 L 70 410 M 85 410 L 100 410 M 85 430 L 70 450 M 85 430 L 100 450" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 457px; margin-left: 85px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
Other Users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="85" y="469" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Other...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="210" y="10" width="60" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 20px; margin-left: 240px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
fake site
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="240" y="24" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
fake site
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="135" y="10" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 20px; margin-left: 170px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
target site
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="170" y="24" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
target site
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,129 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// https://tools.ietf.org/html/rfc1928
|
||||
|
||||
// 1. client send auth request
|
||||
type authReq struct {
|
||||
VER byte
|
||||
NMETHODS uint8
|
||||
METHODS []byte
|
||||
}
|
||||
|
||||
func (req *authReq) Fulfill(r io.Reader) error {
|
||||
buf := make([]byte, 2)
|
||||
if n, err := io.ReadFull(r, buf); err != nil || n != 2 {
|
||||
return err
|
||||
}
|
||||
|
||||
req.VER = buf[0]
|
||||
req.NMETHODS = buf[1]
|
||||
|
||||
req.METHODS = make([]byte, int(req.NMETHODS))
|
||||
if n, err := io.ReadFull(r, req.METHODS); err != nil || n != len(req.METHODS) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *authReq) IsValid() bool {
|
||||
return r.VER == 5 && r.METHODS[0] == 0
|
||||
}
|
||||
|
||||
// 2. server response auth request
|
||||
type authResp struct {
|
||||
VER byte
|
||||
METHOD byte
|
||||
}
|
||||
|
||||
// 3. client request with target address
|
||||
type reqHead struct {
|
||||
VER byte
|
||||
CMD byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
|
||||
func (r *reqHead) IsValid() bool {
|
||||
return r.VER == 5 && r.CMD == 1
|
||||
}
|
||||
|
||||
// 4. server response with the address that assigned to connect to target address
|
||||
type respHead struct {
|
||||
VER byte
|
||||
REP byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
BND_ADDR [net.IPv4len]byte
|
||||
BND_PORT uint16
|
||||
}
|
||||
|
||||
type addrType interface {
|
||||
Fulfill(r io.Reader) error
|
||||
Addr() (domain string, port uint16)
|
||||
}
|
||||
|
||||
// ATYP:
|
||||
//
|
||||
// 0x01 -> net.IPv4len
|
||||
// 0x03 -> first byte is length
|
||||
// 0x04 -> net.IPv6len
|
||||
type addrTypeIPv4 struct {
|
||||
DST_ADDR [4]byte
|
||||
DST_PORT uint16
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type addrTypeDomain struct {
|
||||
DST_ADDR_LEN uint8
|
||||
DST_ADDR []byte
|
||||
DST_PORT uint16
|
||||
}
|
||||
|
||||
func (a *addrTypeDomain) Fulfill(r io.Reader) error {
|
||||
buf := make([]byte, 1)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.DST_ADDR_LEN = uint8(buf[0])
|
||||
buf = make([]byte, a.DST_ADDR_LEN+2)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.DST_ADDR = buf[:int(a.DST_ADDR_LEN)]
|
||||
a.DST_PORT = binary.BigEndian.Uint16(buf[int(a.DST_ADDR_LEN):])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *addrTypeDomain) Addr() (string, uint16) {
|
||||
return string(a.DST_ADDR[:]), a.DST_PORT
|
||||
}
|
||||
|
||||
type addrTypeIPv6 struct {
|
||||
DST_ADDR [16]byte
|
||||
DST_PORT uint16
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AddrHead struct {
|
||||
addrType
|
||||
}
|
||||
|
||||
func (h *AddrHead) Network() string { return "tcp" }
|
||||
func (h *AddrHead) String() string {
|
||||
host, port := h.Addr()
|
||||
return net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
}
|
||||
|
||||
// Socks5 is a SOCKS5 proxy. It implements the teeconn.Conn interface.
|
||||
// It is used to be a second relay of other proxy tools.
|
||||
// user -> sower -socks5-> third-party proxy -> target
|
||||
type Socks5 struct{}
|
||||
|
||||
func New() *Socks5 {
|
||||
return &Socks5{}
|
||||
}
|
||||
|
||||
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 := new(authReq)
|
||||
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 {
|
||||
return nil, errors.Wrap(err, "write auth")
|
||||
}
|
||||
}
|
||||
|
||||
var addr addrType
|
||||
{ // head
|
||||
head := new(reqHead)
|
||||
if err := binary.Read(conn, binary.BigEndian, head); err != nil || !head.IsValid() {
|
||||
return nil, errors.Errorf("read head: %v, err: %s", head, err)
|
||||
}
|
||||
switch head.ATYP {
|
||||
case 0x01: // IPv4
|
||||
addr = &addrTypeIPv4{}
|
||||
case 0x03: // domain name
|
||||
addr = &addrTypeDomain{}
|
||||
case 0x04: // IPv6
|
||||
addr = &addrTypeIPv6{}
|
||||
default:
|
||||
return nil, errors.New("invalid ATYP")
|
||||
}
|
||||
|
||||
if err := addr.Fulfill(conn); err != nil {
|
||||
return nil, errors.Wrap(err, "read target")
|
||||
}
|
||||
|
||||
if err := binary.Write(conn, binary.BigEndian, succHeadResp); err != nil {
|
||||
return nil, errors.Wrap(err, "write head")
|
||||
}
|
||||
}
|
||||
|
||||
return &AddrHead{
|
||||
addrType: addr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var noAuthReq = struct {
|
||||
VER byte
|
||||
NMETHODS uint8
|
||||
METHODS byte
|
||||
}{5, 1, 0}
|
||||
var domainHead = reqHead{VER: 5, CMD: 1, RSV: 0, ATYP: 3}
|
||||
|
||||
func (s *Socks5) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
|
||||
{ // auth
|
||||
if err := binary.Write(conn, binary.BigEndian, &noAuthReq); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resp := &authResp{}
|
||||
if err := binary.Read(conn, binary.BigEndian, resp); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
{ // head
|
||||
buf := bytes.NewBuffer(make([]byte, 0, binary.Size(domainHead)+1+len(tgtHost)+2))
|
||||
_ = binary.Write(buf, binary.BigEndian, domainHead)
|
||||
buf.WriteByte(uint8(len(tgtHost)))
|
||||
buf.WriteString(tgtHost)
|
||||
buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort)})
|
||||
|
||||
if _, err := conn.Write(buf.Bytes()); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
head := respHead{}
|
||||
if err := binary.Read(conn, binary.BigEndian, &head); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package sower
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// https://en.wikipedia.org/wiki/Domain_Name_System
|
||||
const maxDomainLength = 253
|
||||
|
||||
var headSize = binary.Size(new(Head))
|
||||
|
||||
// action(>=0x80) + checksum + port + target + data
|
||||
// data(HTTP, first byte < 0x7F)
|
||||
type Head struct {
|
||||
Cmd byte
|
||||
Checksum uint64
|
||||
Port uint16
|
||||
TgtAddr [maxDomainLength]byte
|
||||
}
|
||||
|
||||
func (h *Head) Network() string { return "tcp" }
|
||||
func (h *Head) String() string {
|
||||
idx := bytes.IndexRune(h.TgtAddr[:], 0)
|
||||
addr := string(h.TgtAddr[:idx])
|
||||
return net.JoinHostPort(addr, strconv.Itoa(int(h.Port)))
|
||||
}
|
||||
|
||||
type Sower struct {
|
||||
password []byte
|
||||
}
|
||||
|
||||
func New(password string) *Sower {
|
||||
return &Sower{
|
||||
password: []byte(password),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sower) Unwrap(conn net.Conn) (net.Addr, error) {
|
||||
buf := make([]byte, headSize)
|
||||
if n, err := conn.Read(buf); err != nil || n != headSize {
|
||||
return nil, errors.Errorf("n: %d, err: %s", n, err)
|
||||
}
|
||||
|
||||
h := &Head{}
|
||||
_ = binary.Read(bytes.NewReader(buf), binary.BigEndian, h)
|
||||
switch h.Cmd {
|
||||
case 0x80:
|
||||
default:
|
||||
return nil, errors.Errorf("invalid command: %d", h.Cmd)
|
||||
}
|
||||
|
||||
if h.Checksum != sumChecksum(h.TgtAddr, s.password) {
|
||||
return nil, errors.New("auth fail")
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (s *Sower) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
|
||||
tgtAddr := [maxDomainLength]byte{}
|
||||
copy(tgtAddr[:len(tgtHost)], []byte(tgtHost))
|
||||
|
||||
return binary.Write(conn, binary.BigEndian, &Head{
|
||||
Cmd: 0x80,
|
||||
Checksum: sumChecksum(tgtAddr, s.password),
|
||||
Port: tgtPort,
|
||||
TgtAddr: tgtAddr,
|
||||
})
|
||||
}
|
||||
|
||||
func sumChecksum(target [maxDomainLength]byte, password []byte) uint64 {
|
||||
checksum := md5.Sum(append(target[:], password...))
|
||||
checksumVal, _ := binary.Uvarint(checksum[:])
|
||||
return checksumVal
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type Transport interface {
|
||||
Unwrap(conn net.Conn) (net.Addr, error)
|
||||
Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sower-proxy/conns/reread"
|
||||
"github.com/sower-proxy/deferlog/log"
|
||||
"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()
|
||||
|
||||
go func(w net.Conn) {
|
||||
defer w.Close()
|
||||
tran.Wrap(w, "sower", 443)
|
||||
}(w)
|
||||
|
||||
return tran.Unwrap(reread.New(r))
|
||||
}
|
||||
|
||||
func Test_Transports(t *testing.T) {
|
||||
if addr, err := testPipe(newSower()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" {
|
||||
t.Errorf("test sower, unexpected address: %s, err: %s", addr, err)
|
||||
}
|
||||
|
||||
if addr, err := testPipe(newTrojan()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" {
|
||||
t.Errorf("test trojan, unexpected address: %s, err: %s", addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
func newSower() *sower.Sower {
|
||||
return sower.New("123")
|
||||
}
|
||||
|
||||
func newTrojan() *trojan.Trojan {
|
||||
return trojan.New("123")
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package trojan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// +-----------------------+---------+----------------+---------+----------+
|
||||
// | hex(SHA224(password)) | CRLF | Trojan Request | CRLF | Payload |
|
||||
// +-----------------------+---------+----------------+---------+----------+
|
||||
// | 56 | X'0D0A' | Variable | X'0D0A' | Variable |
|
||||
// +-----------------------+---------+----------------+---------+----------+
|
||||
// +-----+------+----------+----------+
|
||||
// | CMD | ATYP | DST.ADDR | DST.PORT |
|
||||
// +-----+------+----------+----------+
|
||||
// | 1 | 1 | Variable | 2 |
|
||||
// +-----+------+----------+----------+
|
||||
// o CMD
|
||||
// o CONNECT X'01'
|
||||
// o UDP :X'03'
|
||||
// o ATYP
|
||||
// o IP V4 : X'01'
|
||||
// o domain: X'03'
|
||||
// o IP V6 : X'04'
|
||||
|
||||
const headLen = 56 + 2 + 1 + 1
|
||||
|
||||
type staticHead struct {
|
||||
Passwd [56]byte
|
||||
CRLF [2]byte
|
||||
CMD uint8
|
||||
ATYP uint8
|
||||
}
|
||||
|
||||
type ipv4Addr struct {
|
||||
ADDR [4]byte
|
||||
PORT uint16
|
||||
CRLF [2]byte
|
||||
}
|
||||
|
||||
func (*ipv4Addr) Network() string { return "tcp" }
|
||||
func (a *ipv4Addr) String() string {
|
||||
return net.JoinHostPort(net.IP(a.ADDR[:]).String(), strconv.Itoa(int(a.PORT)))
|
||||
}
|
||||
|
||||
type ipv6Addr struct {
|
||||
ADDR [16]byte
|
||||
PORT uint16
|
||||
CRLF [2]byte
|
||||
}
|
||||
|
||||
func (*ipv6Addr) Network() string { return "tcp" }
|
||||
func (a *ipv6Addr) String() string {
|
||||
return net.JoinHostPort(net.IP(a.ADDR[:]).String(), strconv.Itoa(int(a.PORT)))
|
||||
}
|
||||
|
||||
type domain struct {
|
||||
ADDR string
|
||||
PORT uint16
|
||||
CRLF [2]byte
|
||||
}
|
||||
|
||||
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 := 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 := io.ReadFull(r, buf); err != nil || n != addrLen+4 {
|
||||
return errors.Wrap(err, "read doamin failed")
|
||||
}
|
||||
|
||||
a.ADDR = string(buf[:addrLen])
|
||||
a.PORT = uint16(buf[addrLen])<<8 + uint16(buf[addrLen+1])
|
||||
return nil
|
||||
}
|
||||
|
||||
type Trojan struct {
|
||||
headPasswd []byte
|
||||
|
||||
headIPv4 []byte
|
||||
headIPv6 []byte
|
||||
headDomain []byte
|
||||
}
|
||||
|
||||
func New(password string) *Trojan {
|
||||
t := &Trojan{
|
||||
headPasswd: make([]byte, 56),
|
||||
}
|
||||
passSum := sha256.Sum224([]byte(password))
|
||||
hex.Encode(t.headPasswd, passSum[:])
|
||||
|
||||
t.headIPv4 = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x01)
|
||||
t.headIPv6 = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x04)
|
||||
t.headDomain = append(t.headPasswd, 0x0D, 0x0A, 0x01, 0x03)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Trojan) Unwrap(conn net.Conn) (net.Addr, error) {
|
||||
buf := make([]byte, headLen)
|
||||
// do not use io.ReadFull to avoid hang
|
||||
if n, err := conn.Read(buf); err != nil || n != headLen {
|
||||
return nil, errors.Errorf("n: %d, err: %s", n, err)
|
||||
}
|
||||
|
||||
head := &staticHead{}
|
||||
if err := binary.Read(bytes.NewBuffer(buf), binary.BigEndian, head); err != nil {
|
||||
return nil, errors.Wrap(err, "read head")
|
||||
}
|
||||
|
||||
if !bytes.Equal(head.Passwd[:], []byte(t.headPasswd)) {
|
||||
return nil, errors.New("auth fail")
|
||||
}
|
||||
|
||||
head.CMD, head.ATYP = buf[58], buf[59]
|
||||
switch head.ATYP {
|
||||
case 0x01: // ipv4
|
||||
addr := &ipv4Addr{}
|
||||
err := binary.Read(conn, binary.BigEndian, addr)
|
||||
return addr, errors.Wrap(err, "read addr")
|
||||
|
||||
case 0x04: // ipv6
|
||||
addr := &ipv6Addr{}
|
||||
err := binary.Read(conn, binary.BigEndian, addr)
|
||||
return addr, errors.Wrap(err, "read addr")
|
||||
|
||||
case 0x03: // domain
|
||||
addr := &domain{}
|
||||
err := addr.Fulfill(conn)
|
||||
return addr, errors.Wrap(err, "read addr")
|
||||
|
||||
default:
|
||||
return nil, errors.New("invalid ATYP")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trojan) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, headLen+1+len(tgtHost)+4))
|
||||
ip := net.ParseIP(tgtHost)
|
||||
switch {
|
||||
case len(ip.To4()) != 0:
|
||||
buf.Write(t.headIPv4)
|
||||
buf.Write([]byte(ip.To4()))
|
||||
|
||||
case len(ip) != 0:
|
||||
buf.Write(t.headIPv6)
|
||||
buf.Write([]byte(ip.To16()))
|
||||
|
||||
default:
|
||||
buf.Write(t.headDomain)
|
||||
buf.WriteByte(byte(len(tgtHost)))
|
||||
buf.WriteString(tgtHost)
|
||||
}
|
||||
|
||||
buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort), 0x0D, 0x0A})
|
||||
|
||||
if n, err := conn.Write(buf.Bytes()); err != nil || n != len(buf.Bytes()) {
|
||||
return errors.Errorf("n: %d, err: %s", n, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user