Compare commits
347 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4214ec4239 | ||
|
|
504de47999 | ||
|
|
e185d734d0 | ||
|
|
9b1ef52649 | ||
|
|
5c9fc850d8 | ||
|
|
ff96e52a33 | ||
|
|
78004bcd39 | ||
|
|
b16decf976 | ||
|
|
828636553d | ||
|
|
bfcc27e70f | ||
|
|
7cb7d34d42 | ||
|
|
5276154401 | ||
|
|
dad091441e | ||
|
|
81ff3dadd5 | ||
|
|
f559fb1cae | ||
|
|
8649bbc191 | ||
|
|
d775339948 | ||
|
|
69a5b906e0 | ||
|
|
2d66cc6215 | ||
|
|
8d74baf48c | ||
|
|
d7641c4483 | ||
|
|
ffe54c3af7 | ||
|
|
53df3b5578 | ||
|
|
54c22f1410 | ||
|
|
366b7e04f3 | ||
|
|
a33a4d2bd3 | ||
|
|
eb00d570a8 | ||
|
|
500142f4c8 | ||
|
|
bffd5891cc | ||
|
|
cf2e6f9ff0 | ||
|
|
ed4b8d11e3 | ||
|
|
905c1eac63 | ||
|
|
61872133b1 | ||
|
|
e18f53a5bb | ||
|
|
947fb51963 | ||
|
|
7aeef3f8ba | ||
|
|
b42f6a6364 | ||
|
|
92f4d31dfc | ||
|
|
4f11593f26 | ||
|
|
795d63879f | ||
|
|
1c46eeaf43 | ||
|
|
6f47d12498 | ||
|
|
e6c56675ca | ||
|
|
ff92c96d8d | ||
|
|
fed2afb964 | ||
|
|
b3feff7843 | ||
|
|
edb2fb3458 | ||
|
|
dc51a0bd9d | ||
|
|
34b30ac8c9 | ||
|
|
8122af9096 | ||
|
|
2b267fe4bb | ||
|
|
90bf483976 | ||
|
|
b109f273a5 | ||
|
|
482977a4ac | ||
|
|
515dcdbf1f | ||
|
|
0e033c1d85 | ||
|
|
ad2441de3b | ||
|
|
7a881b3625 | ||
|
|
faf61fdd60 | ||
|
|
2b3d23f77c | ||
|
|
06e1247706 | ||
|
|
6b5fdef8c7 | ||
|
|
48b1621ee4 | ||
|
|
c2ce973f61 | ||
|
|
cac648cc32 | ||
|
|
b9bffcdf4b | ||
|
|
7a1e5c5de7 | ||
|
|
e24dfc856a | ||
|
|
66a115c764 | ||
|
|
a6e80a30dc | ||
|
|
52a2771382 | ||
|
|
6b2b75bc50 | ||
|
|
893baff6c6 | ||
|
|
de62d956dd | ||
|
|
b59cf1f144 | ||
|
|
8b5cc3fb89 | ||
|
|
fbd8c67649 | ||
|
|
c6f6266592 | ||
|
|
ab72640ffd | ||
|
|
905bfff92b | ||
|
|
4ef33d0ffd | ||
|
|
1597363dd1 | ||
|
|
27896a0563 | ||
|
|
bef385cfd1 | ||
|
|
98e0154baa | ||
|
|
01961a798a | ||
|
|
d5a460bd09 | ||
|
|
e94605644f | ||
|
|
8dc206e2d6 | ||
|
|
e8acbbfabf | ||
|
|
17335eb92b | ||
|
|
d2051e6e37 | ||
|
|
0aa0e7c550 | ||
|
|
57935c2296 | ||
|
|
9d4930b29d | ||
|
|
66206e63b3 | ||
|
|
19baccb91a | ||
|
|
98bbd68448 | ||
|
|
45dee58203 | ||
|
|
e2557c44c4 | ||
|
|
f0ed6d73e4 | ||
|
|
47790d1d58 | ||
|
|
9aff5eda38 | ||
|
|
eb2f055e07 | ||
|
|
0998c06195 | ||
|
|
cf22866b2a | ||
|
|
16bb452640 | ||
|
|
6f1d826ef5 | ||
|
|
5a68fb3c3d | ||
|
|
350eb5b6ed | ||
|
|
d48f3b3323 | ||
|
|
7a1491c7b3 | ||
|
|
68deae6bf8 | ||
|
|
2086966a89 | ||
|
|
3aba428b76 | ||
|
|
a11ce38747 | ||
|
|
f7b363ec73 | ||
|
|
4c33a1e9b2 | ||
|
|
c4d9382ac7 | ||
|
|
7df2d990e7 | ||
|
|
900b75ddcd | ||
|
|
a5d199fb1c | ||
|
|
2d8190873f | ||
|
|
3eff793ac2 | ||
|
|
27ce6e1bd2 | ||
|
|
b11c7e632b | ||
|
|
d911be7d80 | ||
|
|
bd056d74cc | ||
|
|
69b65a37ca | ||
|
|
dd52ad8a8a | ||
|
|
59b5ef2df4 | ||
|
|
4a34566f08 | ||
|
|
d81d5ffe06 | ||
|
|
59c9148875 | ||
|
|
40bce3e736 | ||
|
|
d4c0775b4a | ||
|
|
7f983152b7 | ||
|
|
bab4325414 | ||
|
|
0d85c7dd7d | ||
|
|
f756d62b19 | ||
|
|
2d1c1449aa | ||
|
|
f348298acd | ||
|
|
ad47104fc7 | ||
|
|
4d4fb64b59 | ||
|
|
9ce3a6e468 | ||
|
|
34e9e362b9 | ||
|
|
f87cbf73e8 | ||
|
|
362ada2ebb | ||
|
|
b4b7221dab | ||
|
|
2d8dc56f4e | ||
|
|
1cf4313d12 | ||
|
|
7bb8f19b90 | ||
|
|
1263a4e751 | ||
|
|
1a432a9b79 | ||
|
|
8951fdbd59 | ||
|
|
77129367fe | ||
|
|
ae2e1e0933 | ||
|
|
4143f14fbd | ||
|
|
8e3e262c2f | ||
|
|
6f11deab96 | ||
|
|
a17acd7351 | ||
|
|
1cbb4195e4 | ||
|
|
c471dd8297 | ||
|
|
25deffb7d6 | ||
|
|
7eb0e0040e | ||
|
|
15994988be | ||
|
|
ae293a6102 | ||
|
|
5ed4702b62 | ||
|
|
287ddc3424 | ||
|
|
57a4227007 | ||
|
|
c9eacd1bf2 | ||
|
|
5f38162fbb | ||
|
|
c755f75a11 | ||
|
|
ac9eb64501 | ||
|
|
3dd013c13c | ||
|
|
ab0205587a | ||
|
|
70955878c9 | ||
|
|
86f017d92f | ||
|
|
446cc3f9a7 | ||
|
|
6529921d71 | ||
|
|
52e441c111 | ||
|
|
a6b169d336 | ||
|
|
5514cfee6c | ||
|
|
b1de184bda | ||
|
|
af2405ba48 | ||
|
|
bee80330b0 | ||
|
|
f1de8659b7 | ||
|
|
bfc5835d82 | ||
|
|
82bc3e27d6 | ||
|
|
5436a95430 | ||
|
|
2c675f2cbe | ||
|
|
8e9427b0c0 | ||
|
|
edcf78f77c | ||
|
|
8f88d14c07 | ||
|
|
1372801b6f | ||
|
|
2af904f442 | ||
|
|
885b27e0d1 | ||
|
|
0207e4731f | ||
|
|
038b6749a3 | ||
|
|
ead577cbfb | ||
|
|
26e9231e48 | ||
|
|
938ddd1141 | ||
|
|
68080539f7 | ||
|
|
5583b303be | ||
|
|
9301c9b49b | ||
|
|
8d2e210522 | ||
|
|
32661552ff | ||
|
|
9a111a59bf | ||
|
|
f5e472ea9f | ||
|
|
7599e2c793 | ||
|
|
3726f5b9c3 | ||
|
|
dee517217e | ||
|
|
983912e44e | ||
|
|
982390f4b2 | ||
|
|
5e3f51a8b0 | ||
|
|
675ae276f9 | ||
|
|
7899c45176 | ||
|
|
1dbb6feb57 | ||
|
|
661685d136 | ||
|
|
28947a0352 | ||
|
|
bd594684ce | ||
|
|
fda3609873 | ||
|
|
d20db0c546 | ||
|
|
07fb22ae70 | ||
|
|
8d5c3944ad | ||
|
|
7cf28aa9f4 | ||
|
|
731867b73c | ||
|
|
1c6df2d9a2 | ||
|
|
ee5a248a39 | ||
|
|
c174f85656 | ||
|
|
2669aac7c9 | ||
|
|
f1aec74b11 | ||
|
|
1d382b2bf6 | ||
|
|
55cac537b1 | ||
|
|
75258fa195 | ||
|
|
cef2ca6d8e | ||
|
|
4b1651bb3e | ||
|
|
78c116bca9 | ||
|
|
a7c46f5582 | ||
|
|
f947d35bc3 | ||
|
|
a49e0166d4 | ||
|
|
7272b592d5 | ||
|
|
cced739d0e | ||
|
|
54ac46b3e4 | ||
|
|
8f9aa2fd64 | ||
|
|
c7b9cd5853 | ||
|
|
f9dfac55b0 | ||
|
|
b4ad1b5465 | ||
|
|
e2b2b7e255 | ||
|
|
80b691564c | ||
|
|
dfc326b771 | ||
|
|
7cfde70a9f | ||
|
|
20837ba983 | ||
|
|
8dda32a599 | ||
|
|
42ce2a4351 | ||
|
|
6574d5cd29 | ||
|
|
4e9ae9a8f5 | ||
|
|
a9ce3cf733 | ||
|
|
65708a0f12 | ||
|
|
e2a3b5f9ee | ||
|
|
7a752537c5 | ||
|
|
abd0b63fe9 | ||
|
|
7ac7cd452b | ||
|
|
94cecdb8c0 | ||
|
|
a8b35ba971 | ||
|
|
bc1ab84b75 | ||
|
|
acc895d2df | ||
|
|
23dbd0a92f | ||
|
|
c069b5cd97 | ||
|
|
f1dfe50d8b | ||
|
|
7d3820175f | ||
|
|
75032fdbb7 | ||
|
|
23b3ad63cf | ||
|
|
7afd0c86cd | ||
|
|
5f0a341d22 | ||
|
|
2a117376b7 | ||
|
|
2fc750532d | ||
|
|
477be63cff | ||
|
|
39b90357db | ||
|
|
2bd916eb73 | ||
|
|
4d1b450b33 | ||
|
|
cb70812cb7 | ||
|
|
42e030e368 | ||
|
|
f84cdc921d | ||
|
|
02c07e7f4f | ||
|
|
d6ea190688 | ||
|
|
b20487b928 | ||
|
|
004cf5693f | ||
|
|
ef8de6feb0 | ||
|
|
d791ebe634 | ||
|
|
5af4a1817e | ||
|
|
e97b8c55f3 | ||
|
|
99fcb76210 | ||
|
|
d4fd34165e | ||
|
|
bd4741a0a0 | ||
|
|
5945c32646 | ||
|
|
9a9dc2594d | ||
|
|
02547e9475 | ||
|
|
d81a823da1 | ||
|
|
df74bcc885 | ||
|
|
094bcebfa3 | ||
|
|
bb2a16720b | ||
|
|
86d9a0c0f3 | ||
|
|
5cf9d72ed3 | ||
|
|
801605676c | ||
|
|
a9dec75e59 | ||
|
|
08d9d90fe1 | ||
|
|
9749db9235 | ||
|
|
11073aaaa5 | ||
|
|
e35ddc4d53 | ||
|
|
99b06e813e | ||
|
|
7164349944 | ||
|
|
bf43b3adee | ||
|
|
6aa4b3c8a9 | ||
|
|
7a9f7ef95e | ||
|
|
ee1a9d3ec7 | ||
|
|
6a69e58be5 | ||
|
|
6e1d788677 | ||
|
|
24f8f789c5 | ||
|
|
2fb779f990 | ||
|
|
0a9d3cd309 | ||
|
|
977b1aba1c | ||
|
|
a02aeeb906 | ||
|
|
7e2e63137e | ||
|
|
4b35219c27 | ||
|
|
0247c4701d | ||
|
|
e2cd0b8e4f | ||
|
|
ee93171c63 | ||
|
|
ddd2302cb2 | ||
|
|
c96d2288b3 | ||
|
|
6f5a088091 | ||
|
|
9a07797e29 | ||
|
|
055a020d33 | ||
|
|
4681ff3827 | ||
|
|
cff92faf06 | ||
|
|
890daf5489 | ||
|
|
182bdeb766 | ||
|
|
a4a953b167 | ||
|
|
ff37b7e18c | ||
|
|
7aa0e78c15 | ||
|
|
d798807693 | ||
|
|
35b78c2da6 | ||
|
|
66a4291c97 | ||
|
|
e89a965aff | ||
|
|
85a9f10be4 | ||
|
|
8bc6e0ffec | ||
|
|
98fc0ade4a |
3
.gitignore
vendored
@ -1,7 +1,10 @@
|
|||||||
proxy
|
proxy
|
||||||
|
goproxy
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
.*
|
.*
|
||||||
|
*.prof
|
||||||
|
!.gitignore
|
||||||
release-*
|
release-*
|
||||||
proxy.crt
|
proxy.crt
|
||||||
proxy.key
|
proxy.key
|
||||||
|
|||||||
107
CHANGELOG
@ -1,4 +1,111 @@
|
|||||||
proxy更新日志
|
proxy更新日志
|
||||||
|
v4.9
|
||||||
|
1.修复了HTTP Basic代理返回不合适的头部,导致浏览器不会弹框,个别代理插件无法认证的问题.
|
||||||
|
2.内网穿透切换smux到yamux.
|
||||||
|
3.优化了HTTP(S)\SOCKS5代理--always的处理逻辑.
|
||||||
|
|
||||||
|
v4.8
|
||||||
|
1.优化了SPS连接HTTP上级的指令,避免了某些代理不响应的问题.
|
||||||
|
2.SPS功能增加了参数:
|
||||||
|
--disable-http:禁用http(s)代理
|
||||||
|
--disable-socks:禁用socks代理
|
||||||
|
默认都是false(开启).
|
||||||
|
3.重构了部分代码的日志部分,保证了日志按着预期输出.
|
||||||
|
4.修复了sps\http代理初始化服务的时机不正确,导致nil异常的bug.
|
||||||
|
5.优化了sps日志输出.
|
||||||
|
6.--debug参数增加了Profiling功能,可以保存cpu,内存等多种调试数据到文件.
|
||||||
|
7.优化了服务注册,避免了不必要的内存开销.
|
||||||
|
8.增加了Dockerfile和docker安装手册.
|
||||||
|
9.优化了ioCopy避免了内存泄漏,大大提升了内存占用的稳定性.
|
||||||
|
|
||||||
|
|
||||||
|
v4.7
|
||||||
|
1.增加了基于gomobile的sdk,对android/ios/windows/linux/mac提供SDK支持.
|
||||||
|
2.优化了bridge的日志,增加了client和server的掉线日志.
|
||||||
|
3.优化了sps读取http(s)代理响应的缓冲大小,同时优化了CONNECT请求,
|
||||||
|
避免了某些代理服务器返回过多数据导致不能正常通讯的问题.
|
||||||
|
4.去除了鸡肋连接池功能.
|
||||||
|
5.优化了所有服务代码,方便对sdk提供支持.
|
||||||
|
6.增加了SDK手册.
|
||||||
|
7.增加了GUI客户端(windows/web/android/ios)介绍主页.
|
||||||
|
8.SPS\HTTP(s)\Socks代理增加了自定义加密传输,只需要通过参数-z和-Z设置一个密码即可.
|
||||||
|
9.SPS\HTTP(s)\Socks代理增加了压缩传输,只需要通过参数-m和-M设置即可.
|
||||||
|
10.手册增加了SPS\HTTP(s)\Socks自定义加密的使用示例.
|
||||||
|
11.手册增加了SPS\HTTP(s)\Socks压缩传输的使用示例.
|
||||||
|
12.优化了多链接版本的内网穿透,融合了多链接和smux的优点,即能够拥有大的吞吐量,
|
||||||
|
同时又具备mux的心跳机制保证了链接的稳定性.
|
||||||
|
13.手册增加了大量配图.
|
||||||
|
14.优化了socks代理udp上级的设置逻辑,智能判断parent上级填充udp parent.
|
||||||
|
15.优化了项目文件夹结构,使用源码可以直接go get.
|
||||||
|
|
||||||
|
v4.6
|
||||||
|
1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定.
|
||||||
|
2.sps增加了强大的树形级联认证支持,可以轻松构建你的认证代理网络.
|
||||||
|
3.手册增加了6.6对sps认证功能的介绍.
|
||||||
|
|
||||||
|
|
||||||
|
v4.5
|
||||||
|
1.优化了mux内网穿透连接管理逻辑,增强了稳定性.
|
||||||
|
2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp.
|
||||||
|
3.keygen参数增加了用法: proxy keygen usage.
|
||||||
|
4.http(s)/socks5代理,tls增加了自签名证书支持.
|
||||||
|
5.建议升级.
|
||||||
|
v4.4
|
||||||
|
1.增加了协议转换sps功能,代理协议转换使用的是sps子命令(socks+https的缩写),
|
||||||
|
sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理
|
||||||
|
或者socks5代理;sps可以把已经存在的http(s)代理或者socks5代理转换为一个端口
|
||||||
|
同时支持http(s)和socks5代理,而且http(s)代理支持正向代理和反向代理(SNI),转
|
||||||
|
换后的SOCKS5代理不支持UDP功能;另外对于已经存在的http(s)代理或者socks5代理,
|
||||||
|
支持tls、tcp、kcp三种模式,支持链式连接,也就是可以多个sps结点层级连接构建
|
||||||
|
加密通道。
|
||||||
|
2.增加了对KCP传输参数的配置,多达17个参数可以自由的配置对kcp传输效率调优。
|
||||||
|
3.内网穿透功能,server和client增加了--session-count参数,可以设置server每个
|
||||||
|
监听端口到bridge打开的session数量,可以设置client到bridge打开的session数量,
|
||||||
|
之前都是1个,现在性能提升N倍,N就是你自己设置的--session-count,这个参数很大
|
||||||
|
程度上解决了多路复用的拥塞问题,v4.4开始默认10个。
|
||||||
|
|
||||||
|
v4.3
|
||||||
|
1.优化了参数keygen生成证书逻辑,避免证书出现特征。
|
||||||
|
2.http(s)和socks代理增加了--dns-address和--dns-ttl参数。
|
||||||
|
用于自己指定proxy访问域名的时候使用的dns(--dns-address)以及解析结果缓存时间(--dns-ttl)秒数,
|
||||||
|
避免系统dns对proxy的干扰,另外缓存功能还能减少dns解析时间提高访问速度。
|
||||||
|
3.优化了http代理的basic认证逻辑。
|
||||||
|
提示:
|
||||||
|
v4.3生成的证书不适用于v4.2及以下版本。
|
||||||
|
|
||||||
|
v4.2
|
||||||
|
1.优化了内网穿透,避免了client意外下线,导致链接信息残留的问题.
|
||||||
|
2.http代理增加了SNI支持,现在http(s)代理模式支持反向代理,支持http(s)透明代理.
|
||||||
|
3.增加了英文手册.
|
||||||
|
|
||||||
|
v4.1
|
||||||
|
1.优化了http(s),socks5代理中的域名智能判断,如果是内网IP,直接走本地网络,提升浏览体验,
|
||||||
|
同时优化了检查机制,判断更快.
|
||||||
|
2.http代理basic认证增加了对https协议的支持,现在basic认证可以控制所有http(s)流量了.
|
||||||
|
3.项目代码增加了依赖类库vendor目录,clone下来就能go build,再也不用担心go get依赖类库
|
||||||
|
失败导致不能编译了.
|
||||||
|
|
||||||
|
v4.0
|
||||||
|
1.内网穿透三端重构了一个multiplexing版本,使用github.com/xtaci/smux实现了tcp链接的多路复用,
|
||||||
|
鼎鼎大名的kcp-go底层就是使用的这个库,基于kcp-go的双边加速工具kcptun的广泛使用已经很好
|
||||||
|
的验证来该库的强大与稳定。multiplexing版的内网穿透对应的子命令分别是server,client,bridge
|
||||||
|
使用方式和参数与之前的子命令tserver,tclient,tserver完全一样,另外server,client增加了
|
||||||
|
压缩传输参数--c,使用压缩传输速度更快。
|
||||||
|
|
||||||
|
v3.9
|
||||||
|
1.增加了守护运行参数--forever,比如: proxy http --forever ,
|
||||||
|
proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后重启子进程.
|
||||||
|
该参数配合后台运行参数--daemon和日志参数--log,可以保障proxy一直在后台执行不会因为意外退出,
|
||||||
|
而且可以通过日志文件看到proxy的输出日志内容.
|
||||||
|
比如: proxy http -p ":9090" --forever --log proxy.log --daemon
|
||||||
|
|
||||||
|
v3.8
|
||||||
|
1.增加了日志输出到文件--log参数,比如: --log proxy.log,日志就会输出到proxy.log方便排除问题.
|
||||||
|
|
||||||
|
v3.7
|
||||||
|
1.修复了socks代理不能正常和上级代理通讯的问题.
|
||||||
|
|
||||||
|
|
||||||
v3.6
|
v3.6
|
||||||
1.http(s),socks代理,集成了外部HTTP API认证,可以通过外部API对用户名和密码进行认证.
|
1.http(s),socks代理,集成了外部HTTP API认证,可以通过外部API对用户名和密码进行认证.
|
||||||
2.手册http(s),socks代理认证部分增加了集成外部HTTP API认证的使用说明.
|
2.手册http(s),socks代理认证部分增加了集成外部HTTP API认证的使用说明.
|
||||||
|
|||||||
9
Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM golang:1.8.5-alpine as builder
|
||||||
|
ARG GOPROXY_VERSION=master
|
||||||
|
RUN apk update && apk upgrade && \
|
||||||
|
apk add --no-cache git && cd /go/src/ && git clone https://github.com/snail007/goproxy && \
|
||||||
|
cd goproxy && git checkout ${GOPROXY_VERSION} && \
|
||||||
|
go get && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o proxy
|
||||||
|
FROM alpine:3.7
|
||||||
|
COPY --from=builder /go/src/goproxy/proxy /
|
||||||
|
CMD /proxy ${OPTS}
|
||||||
146
Godeps/Godeps.json
generated
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/snail007/goproxy",
|
||||||
|
"GoVersion": "go1.8",
|
||||||
|
"GodepVersion": "v80",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Yawning/chacha20",
|
||||||
|
"Rev": "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/alecthomas/template",
|
||||||
|
"Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/alecthomas/template/parse",
|
||||||
|
"Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/alecthomas/units",
|
||||||
|
"Rev": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/snappy",
|
||||||
|
"Rev": "553a641470496b2327abcac10b36396bd98e45c9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/miekg/dns",
|
||||||
|
"Comment": "v1.0.4-1-g40b5202",
|
||||||
|
"Rev": "40b520211179dbf7eaafaa7fe1ffaa1b7d929ee0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pkg/errors",
|
||||||
|
"Comment": "v0.8.0-6-g602255c",
|
||||||
|
"Rev": "602255cdb6deaf1523ea53ac30eae5554ba7bee9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/templexxx/cpufeat",
|
||||||
|
"Rev": "3794dfbfb04749f896b521032f69383f24c3687e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/templexxx/reedsolomon",
|
||||||
|
"Comment": "0.1.1-4-g7092926",
|
||||||
|
"Rev": "7092926d7d05c415fabb892b1464a03f8228ab80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/templexxx/xor",
|
||||||
|
"Comment": "0.1.2",
|
||||||
|
"Rev": "0af8e873c554da75f37f2049cdffda804533d44c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/tjfoc/gmsm/sm4",
|
||||||
|
"Comment": "v1.0.1-3-g9d99fac",
|
||||||
|
"Rev": "9d99face20b0dd300b7db50b3f69758de41c096a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/xtaci/kcp-go",
|
||||||
|
"Comment": "v3.19-6-g21da33a",
|
||||||
|
"Rev": "21da33a6696d67c1bffb3c954366499d613097a6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/xtaci/smux",
|
||||||
|
"Comment": "v1.0.6",
|
||||||
|
"Rev": "ebec7ef2574b42a7088cd7751176483e0a27d458"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/cast5",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/curve25519",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ed25519",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ed25519/internal/edwards25519",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/salsa20",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ssh",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/tea",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/twofish",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/xtea",
|
||||||
|
"Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/bpf",
|
||||||
|
"Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/internal/iana",
|
||||||
|
"Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/internal/socket",
|
||||||
|
"Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/ipv4",
|
||||||
|
"Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/ipv6",
|
||||||
|
"Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/time/rate",
|
||||||
|
"Rev": "6dc17368e09b0e8634d71cac8168d853e869a0c7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/alecthomas/kingpin.v2",
|
||||||
|
"Comment": "v2.2.5",
|
||||||
|
"Rev": "1087e65c9441605df944fb12c33f0fe7072d18ca"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5
Godeps/Readme
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
||||||
1074
README_ZH.md
Normal file
331
config.go
@ -1,19 +1,30 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
logger "log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"proxy/services"
|
"runtime/pprof"
|
||||||
"proxy/utils"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services"
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
kcp "github.com/xtaci/kcp-go"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
app *kingpin.Application
|
app *kingpin.Application
|
||||||
service *services.ServiceItem
|
service *services.ServiceItem
|
||||||
|
cmd *exec.Cmd
|
||||||
|
cpuProfilingFile, memProfilingFile, blockProfilingFile, goroutineProfilingFile, threadcreateProfilingFile *os.File
|
||||||
|
isDebug bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func initConfig() (err error) {
|
func initConfig() (err error) {
|
||||||
@ -31,16 +42,42 @@ func initConfig() (err error) {
|
|||||||
tunnelServerArgs := services.TunnelServerArgs{}
|
tunnelServerArgs := services.TunnelServerArgs{}
|
||||||
tunnelClientArgs := services.TunnelClientArgs{}
|
tunnelClientArgs := services.TunnelClientArgs{}
|
||||||
tunnelBridgeArgs := services.TunnelBridgeArgs{}
|
tunnelBridgeArgs := services.TunnelBridgeArgs{}
|
||||||
|
muxServerArgs := services.MuxServerArgs{}
|
||||||
|
muxClientArgs := services.MuxClientArgs{}
|
||||||
|
muxBridgeArgs := services.MuxBridgeArgs{}
|
||||||
udpArgs := services.UDPArgs{}
|
udpArgs := services.UDPArgs{}
|
||||||
socksArgs := services.SocksArgs{}
|
socksArgs := services.SocksArgs{}
|
||||||
|
spsArgs := services.SPSArgs{}
|
||||||
|
kcpArgs := kcpcfg.KCPConfigArgs{}
|
||||||
//build srvice args
|
//build srvice args
|
||||||
app = kingpin.New("proxy", "happy with proxy")
|
app = kingpin.New("proxy", "happy with proxy")
|
||||||
app.Author("snail").Version(APP_VERSION)
|
app.Author("snail").Version(APP_VERSION)
|
||||||
debug := app.Flag("debug", "debug log output").Default("false").Bool()
|
debug := app.Flag("debug", "debug log output").Default("false").Bool()
|
||||||
daemon := app.Flag("daemon", "run proxy in background").Default("false").Bool()
|
daemon := app.Flag("daemon", "run proxy in background").Default("false").Bool()
|
||||||
|
forever := app.Flag("forever", "run proxy in forever,fail and retry").Default("false").Bool()
|
||||||
|
logfile := app.Flag("log", "log file path").Default("").String()
|
||||||
|
kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String()
|
||||||
|
kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none")
|
||||||
|
kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").Enum("fast3", "fast2", "fast", "normal", "manual")
|
||||||
|
kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int()
|
||||||
|
kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int()
|
||||||
|
kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int()
|
||||||
|
kcpArgs.DataShard = app.Flag("kcp-ds", "set reed-solomon erasure coding - datashard").Default("10").Int()
|
||||||
|
kcpArgs.ParityShard = app.Flag("kcp-ps", "set reed-solomon erasure coding - parityshard").Default("3").Int()
|
||||||
|
kcpArgs.DSCP = app.Flag("kcp-dscp", "set DSCP(6bit)").Default("0").Int()
|
||||||
|
kcpArgs.NoComp = app.Flag("kcp-nocomp", "disable compression").Default("false").Bool()
|
||||||
|
kcpArgs.AckNodelay = app.Flag("kcp-acknodelay", "be carefull! flush ack immediately when a packet is received").Default("true").Bool()
|
||||||
|
kcpArgs.NoDelay = app.Flag("kcp-nodelay", "be carefull!").Default("0").Int()
|
||||||
|
kcpArgs.Interval = app.Flag("kcp-interval", "be carefull!").Default("50").Int()
|
||||||
|
kcpArgs.Resend = app.Flag("kcp-resend", "be carefull!").Default("0").Int()
|
||||||
|
kcpArgs.NoCongestion = app.Flag("kcp-nc", "be carefull! no congestion").Default("0").Int()
|
||||||
|
kcpArgs.SockBuf = app.Flag("kcp-sockbuf", "be carefull!").Default("4194304").Int()
|
||||||
|
kcpArgs.KeepAlive = app.Flag("kcp-keepalive", "be carefull!").Default("10").Int()
|
||||||
|
|
||||||
//########http#########
|
//########http#########
|
||||||
http := app.Command("http", "proxy on http mode")
|
http := app.Command("http", "proxy on http mode")
|
||||||
httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
httpArgs.CaCertFile = http.Flag("ca", "ca cert file for tls").Default("").String()
|
||||||
httpArgs.CertFile = http.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
httpArgs.CertFile = http.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
httpArgs.KeyFile = http.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
httpArgs.KeyFile = http.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
httpArgs.LocalType = http.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
httpArgs.LocalType = http.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
@ -53,20 +90,23 @@ func initConfig() (err error) {
|
|||||||
httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||||
httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||||
httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||||
httpArgs.PoolSize = http.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int()
|
|
||||||
httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||||
httpArgs.Local = http.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||||
httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||||
httpArgs.SSHKeyFile = http.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
httpArgs.SSHKeyFile = http.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||||
httpArgs.SSHKeyFileSalt = http.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
httpArgs.SSHKeyFileSalt = http.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||||
httpArgs.SSHPassword = http.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
httpArgs.SSHPassword = http.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
||||||
httpArgs.KCPKey = http.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String()
|
|
||||||
httpArgs.KCPMethod = http.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String()
|
|
||||||
httpArgs.LocalIPS = http.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
httpArgs.LocalIPS = http.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||||
httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||||
httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||||
httpArgs.AuthURLOkCode = http.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
httpArgs.AuthURLOkCode = http.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||||
httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int()
|
httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int()
|
||||||
|
httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||||
|
httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||||
|
httpArgs.LocalKey = http.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||||
|
httpArgs.ParentKey = http.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||||
|
httpArgs.LocalCompress = http.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||||
|
httpArgs.ParentCompress = http.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||||
|
|
||||||
//########tcp#########
|
//########tcp#########
|
||||||
tcp := app.Command("tcp", "proxy on tcp mode")
|
tcp := app.Command("tcp", "proxy on tcp mode")
|
||||||
@ -76,11 +116,8 @@ func initConfig() (err error) {
|
|||||||
tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int()
|
tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int()
|
||||||
tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type <tls|tcp|kcp|udp>").Short('T').Enum("tls", "tcp", "udp", "kcp")
|
tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type <tls|tcp|kcp|udp>").Short('T').Enum("tls", "tcp", "udp", "kcp")
|
||||||
tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
tcpArgs.PoolSize = tcp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int()
|
|
||||||
tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||||
tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
tcpArgs.KCPKey = tcp.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String()
|
|
||||||
tcpArgs.KCPMethod = tcp.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String()
|
|
||||||
|
|
||||||
//########udp#########
|
//########udp#########
|
||||||
udp := app.Command("udp", "proxy on udp mode")
|
udp := app.Command("udp", "proxy on udp mode")
|
||||||
@ -89,10 +126,41 @@ func initConfig() (err error) {
|
|||||||
udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int()
|
udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int()
|
||||||
udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type <tls|tcp|udp>").Short('T').Enum("tls", "tcp", "udp")
|
udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type <tls|tcp|udp>").Short('T').Enum("tls", "tcp", "udp")
|
||||||
udpArgs.PoolSize = udp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int()
|
|
||||||
udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||||
udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
|
||||||
|
//########mux-server#########
|
||||||
|
muxServer := app.Command("server", "proxy on mux server mode")
|
||||||
|
muxServerArgs.Parent = muxServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
muxServerArgs.ParentType = muxServer.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Default("tls").Short('T').Enum("tls", "tcp", "kcp")
|
||||||
|
muxServerArgs.CertFile = muxServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
muxServerArgs.KeyFile = muxServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||||
|
muxServerArgs.IsUDP = muxServer.Flag("udp", "proxy on udp mux server mode").Default("false").Bool()
|
||||||
|
muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String()
|
||||||
|
muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||||
|
muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||||
|
muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||||
|
|
||||||
|
//########mux-client#########
|
||||||
|
muxClient := app.Command("client", "proxy on mux client mode")
|
||||||
|
muxClientArgs.Parent = muxClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
muxClientArgs.ParentType = muxClient.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Default("tls").Short('T').Enum("tls", "tcp", "kcp")
|
||||||
|
muxClientArgs.CertFile = muxClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
muxClientArgs.KeyFile = muxClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||||
|
muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String()
|
||||||
|
muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||||
|
muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||||
|
|
||||||
|
//########mux-bridge#########
|
||||||
|
muxBridge := app.Command("bridge", "proxy on mux bridge mode")
|
||||||
|
muxBridgeArgs.CertFile = muxBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
muxBridgeArgs.KeyFile = muxBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||||
|
muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
muxBridgeArgs.LocalType = muxBridge.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tls").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
|
||||||
//########tunnel-server#########
|
//########tunnel-server#########
|
||||||
tunnelServer := app.Command("tserver", "proxy on tunnel server mode")
|
tunnelServer := app.Command("tserver", "proxy on tunnel server mode")
|
||||||
tunnelServerArgs.Parent = tunnelServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
tunnelServerArgs.Parent = tunnelServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
@ -101,7 +169,7 @@ func initConfig() (err error) {
|
|||||||
tunnelServerArgs.Timeout = tunnelServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
tunnelServerArgs.Timeout = tunnelServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||||
tunnelServerArgs.IsUDP = tunnelServer.Flag("udp", "proxy on udp tunnel server mode").Default("false").Bool()
|
tunnelServerArgs.IsUDP = tunnelServer.Flag("udp", "proxy on udp tunnel server mode").Default("false").Bool()
|
||||||
tunnelServerArgs.Key = tunnelServer.Flag("k", "client key").Default("default").String()
|
tunnelServerArgs.Key = tunnelServer.Flag("k", "client key").Default("default").String()
|
||||||
tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as :PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||||
|
|
||||||
//########tunnel-client#########
|
//########tunnel-client#########
|
||||||
tunnelClient := app.Command("tclient", "proxy on tunnel client mode")
|
tunnelClient := app.Command("tclient", "proxy on tunnel client mode")
|
||||||
@ -127,6 +195,7 @@ func initConfig() (err error) {
|
|||||||
socksArgs.UDPParent = socks.Flag("udp-parent", "udp parent address, such as: \"23.32.32.19:33090\"").Default("").Short('X').String()
|
socksArgs.UDPParent = socks.Flag("udp-parent", "udp parent address, such as: \"23.32.32.19:33090\"").Default("").Short('X').String()
|
||||||
socksArgs.UDPLocal = socks.Flag("udp-local", "udp local ip:port to listen").Short('x').Default(":33090").String()
|
socksArgs.UDPLocal = socks.Flag("udp-local", "udp local ip:port to listen").Short('x').Default(":33090").String()
|
||||||
socksArgs.CertFile = socks.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
socksArgs.CertFile = socks.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
socksArgs.CaCertFile = socks.Flag("ca", "ca cert file for tls").Default("").String()
|
||||||
socksArgs.KeyFile = socks.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
socksArgs.KeyFile = socks.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
socksArgs.SSHUser = socks.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
socksArgs.SSHUser = socks.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||||
socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||||
@ -139,17 +208,126 @@ func initConfig() (err error) {
|
|||||||
socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||||
socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||||
socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||||
socksArgs.KCPKey = socks.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String()
|
|
||||||
socksArgs.KCPMethod = socks.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String()
|
|
||||||
socksArgs.LocalIPS = socks.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
socksArgs.LocalIPS = socks.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||||
socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||||
socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||||
socksArgs.AuthURLOkCode = socks.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
socksArgs.AuthURLOkCode = socks.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||||
socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||||
|
socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||||
|
socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||||
|
socksArgs.LocalKey = socks.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||||
|
socksArgs.ParentKey = socks.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||||
|
socksArgs.LocalCompress = socks.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||||
|
socksArgs.ParentCompress = socks.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||||
|
|
||||||
|
//########socks+http(s)#########
|
||||||
|
sps := app.Command("sps", "proxy on socks+http(s) mode")
|
||||||
|
spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
spsArgs.CaCertFile = sps.Flag("ca", "ca cert file for tls").Default("").String()
|
||||||
|
spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int()
|
||||||
|
spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Short('T').Enum("tls", "tcp", "kcp")
|
||||||
|
spsArgs.LocalType = sps.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||||
|
spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type <http|socks>").Short('S').Enum("http", "socks", "ss")
|
||||||
|
spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||||
|
spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||||
|
spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||||
|
spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||||
|
spsArgs.LocalIPS = sps.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||||
|
spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||||
|
spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||||
|
spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||||
|
spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||||
|
spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||||
|
spsArgs.LocalKey = sps.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||||
|
spsArgs.ParentKey = sps.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||||
|
spsArgs.LocalCompress = sps.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||||
|
spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||||
|
spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool()
|
||||||
|
spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool()
|
||||||
|
|
||||||
//parse args
|
//parse args
|
||||||
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))
|
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||||
flags := log.Ldate
|
|
||||||
|
isDebug = *debug
|
||||||
|
|
||||||
|
//set kcp config
|
||||||
|
|
||||||
|
switch *kcpArgs.Mode {
|
||||||
|
case "normal":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 40, 2, 1
|
||||||
|
case "fast":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 30, 2, 1
|
||||||
|
case "fast2":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 20, 2, 1
|
||||||
|
case "fast3":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 10, 2, 1
|
||||||
|
}
|
||||||
|
pass := pbkdf2.Key([]byte(*kcpArgs.Key), []byte("snail007-goproxy"), 4096, 32, sha1.New)
|
||||||
|
|
||||||
|
switch *kcpArgs.Crypt {
|
||||||
|
case "sm4":
|
||||||
|
kcpArgs.Block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||||
|
case "tea":
|
||||||
|
kcpArgs.Block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||||
|
case "xor":
|
||||||
|
kcpArgs.Block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||||
|
case "none":
|
||||||
|
kcpArgs.Block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||||
|
case "aes-128":
|
||||||
|
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||||
|
case "aes-192":
|
||||||
|
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||||
|
case "blowfish":
|
||||||
|
kcpArgs.Block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||||
|
case "twofish":
|
||||||
|
kcpArgs.Block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||||
|
case "cast5":
|
||||||
|
kcpArgs.Block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||||
|
case "3des":
|
||||||
|
kcpArgs.Block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||||
|
case "xtea":
|
||||||
|
kcpArgs.Block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||||
|
case "salsa20":
|
||||||
|
kcpArgs.Block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||||
|
default:
|
||||||
|
*kcpArgs.Crypt = "aes"
|
||||||
|
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass)
|
||||||
|
}
|
||||||
|
//attach kcp config
|
||||||
|
tcpArgs.KCP = kcpArgs
|
||||||
|
httpArgs.KCP = kcpArgs
|
||||||
|
socksArgs.KCP = kcpArgs
|
||||||
|
spsArgs.KCP = kcpArgs
|
||||||
|
muxBridgeArgs.KCP = kcpArgs
|
||||||
|
muxServerArgs.KCP = kcpArgs
|
||||||
|
muxClientArgs.KCP = kcpArgs
|
||||||
|
|
||||||
|
log := logger.New(os.Stderr, "", logger.Ldate|logger.Ltime)
|
||||||
|
|
||||||
|
flags := logger.Ldate
|
||||||
|
if *debug {
|
||||||
|
flags |= logger.Lshortfile | logger.Lmicroseconds
|
||||||
|
cpuProfilingFile, _ = os.Create("cpu.prof")
|
||||||
|
memProfilingFile, _ = os.Create("memory.prof")
|
||||||
|
blockProfilingFile, _ = os.Create("block.prof")
|
||||||
|
goroutineProfilingFile, _ = os.Create("goroutine.prof")
|
||||||
|
threadcreateProfilingFile, _ = os.Create("threadcreate.prof")
|
||||||
|
pprof.StartCPUProfile(cpuProfilingFile)
|
||||||
|
} else {
|
||||||
|
flags |= logger.Ltime
|
||||||
|
}
|
||||||
|
log.SetFlags(flags)
|
||||||
|
|
||||||
|
if *logfile != "" {
|
||||||
|
f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||||
|
if e != nil {
|
||||||
|
log.Fatal(e)
|
||||||
|
}
|
||||||
|
log.SetOutput(f)
|
||||||
|
}
|
||||||
if *daemon {
|
if *daemon {
|
||||||
args := []string{}
|
args := []string{}
|
||||||
for _, arg := range os.Args[1:] {
|
for _, arg := range os.Args[1:] {
|
||||||
@ -157,27 +335,103 @@ func initConfig() (err error) {
|
|||||||
args = append(args, arg)
|
args = append(args, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd := exec.Command(os.Args[0], args...)
|
cmd = exec.Command(os.Args[0], args...)
|
||||||
cmd.Start()
|
cmd.Start()
|
||||||
fmt.Printf("%s [PID] %d running...\n", os.Args[0], cmd.Process.Pid)
|
f := ""
|
||||||
|
if *forever {
|
||||||
|
f = "forever "
|
||||||
|
}
|
||||||
|
log.Printf("%s%s [PID] %d running...\n", f, os.Args[0], cmd.Process.Pid)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
if *debug {
|
if *forever {
|
||||||
flags |= log.Lshortfile | log.Lmicroseconds
|
args := []string{}
|
||||||
} else {
|
for _, arg := range os.Args[1:] {
|
||||||
flags |= log.Ltime
|
if arg != "--forever" {
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
cmd = exec.Command(os.Args[0], args...)
|
||||||
|
cmdReaderStderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmdReader, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(cmdReader)
|
||||||
|
scannerStdErr := bufio.NewScanner(cmdReaderStderr)
|
||||||
|
go func() {
|
||||||
|
for scanner.Scan() {
|
||||||
|
fmt.Println(scanner.Text())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for scannerStdErr.Scan() {
|
||||||
|
fmt.Println(scannerStdErr.Text())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pid := cmd.Process.Pid
|
||||||
|
log.Printf("worker %s [PID] %d running...\n", os.Args[0], pid)
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("worker %s [PID] %d unexpected exited, restarting...\n", os.Args[0], pid)
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *logfile == "" {
|
||||||
|
poster()
|
||||||
|
if *debug {
|
||||||
|
log.Println("[profiling] cpu profiling save to file : cpu.prof")
|
||||||
|
log.Println("[profiling] memory profiling save to file : memory.prof")
|
||||||
|
log.Println("[profiling] block profiling save to file : block.prof")
|
||||||
|
log.Println("[profiling] goroutine profiling save to file : goroutine.prof")
|
||||||
|
log.Println("[profiling] threadcreate profiling save to file : threadcreate.prof")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.SetFlags(flags)
|
|
||||||
poster()
|
|
||||||
//regist services and run service
|
//regist services and run service
|
||||||
services.Regist("http", services.NewHTTP(), httpArgs)
|
//regist services and run service
|
||||||
services.Regist("tcp", services.NewTCP(), tcpArgs)
|
switch serviceName {
|
||||||
services.Regist("udp", services.NewUDP(), udpArgs)
|
case "http":
|
||||||
services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs)
|
services.Regist(serviceName, services.NewHTTP(), httpArgs, log)
|
||||||
services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs)
|
case "tcp":
|
||||||
services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs)
|
services.Regist(serviceName, services.NewTCP(), tcpArgs, log)
|
||||||
services.Regist("socks", services.NewSocks(), socksArgs)
|
case "udp":
|
||||||
service, err = services.Run(serviceName)
|
services.Regist(serviceName, services.NewUDP(), udpArgs, log)
|
||||||
|
case "tserver":
|
||||||
|
services.Regist(serviceName, services.NewTunnelServerManager(), tunnelServerArgs, log)
|
||||||
|
case "tclient":
|
||||||
|
services.Regist(serviceName, services.NewTunnelClient(), tunnelClientArgs, log)
|
||||||
|
case "tbridge":
|
||||||
|
services.Regist(serviceName, services.NewTunnelBridge(), tunnelBridgeArgs, log)
|
||||||
|
case "server":
|
||||||
|
services.Regist(serviceName, services.NewMuxServerManager(), muxServerArgs, log)
|
||||||
|
case "client":
|
||||||
|
services.Regist(serviceName, services.NewMuxClient(), muxClientArgs, log)
|
||||||
|
case "bridge":
|
||||||
|
services.Regist(serviceName, services.NewMuxBridge(), muxBridgeArgs, log)
|
||||||
|
case "socks":
|
||||||
|
services.Regist(serviceName, services.NewSocks(), socksArgs, log)
|
||||||
|
case "sps":
|
||||||
|
services.Regist(serviceName, services.NewSPS(), spsArgs, log)
|
||||||
|
}
|
||||||
|
service, err = services.Run(serviceName, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("run service [%s] fail, ERR:%s", serviceName, err)
|
log.Fatalf("run service [%s] fail, ERR:%s", serviceName, err)
|
||||||
}
|
}
|
||||||
@ -196,3 +450,14 @@ func poster() {
|
|||||||
|
|
||||||
v%s`+" by snail , blog : http://www.host900.com/\n\n", APP_VERSION)
|
v%s`+" by snail , blog : http://www.host900.com/\n\n", APP_VERSION)
|
||||||
}
|
}
|
||||||
|
func saveProfiling() {
|
||||||
|
goroutine := pprof.Lookup("goroutine")
|
||||||
|
goroutine.WriteTo(goroutineProfilingFile, 1)
|
||||||
|
heap := pprof.Lookup("heap")
|
||||||
|
heap.WriteTo(memProfilingFile, 1)
|
||||||
|
block := pprof.Lookup("block")
|
||||||
|
block.WriteTo(blockProfilingFile, 1)
|
||||||
|
threadcreate := pprof.Lookup("threadcreate")
|
||||||
|
threadcreate.WriteTo(threadcreateProfilingFile, 1)
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|||||||
BIN
docs/images/1.1.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/images/2.1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/images/2.2.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/5.2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/alipay.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/images/fxdl.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/images/http-1.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/http-2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/http-kcp.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/http-ssh-1.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/images/http-tls-2.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/http-tls-3.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/images/socks-2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/socks-ssh.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/images/socks-tls-2.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/images/socks-tls-3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/sps-tls.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/images/tcp-1.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/tcp-2.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/images/tcp-3.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/tcp-tls-2.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/images/tcp-tls-3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/udp-1.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/images/udp-2.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/images/udp-3.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/images/udp-tls-2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/udp-tls-3.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/images/wxpay.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
27
gui/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Proxy-GUI
|
||||||
|
基于proxy的各平台SDK,作者和众多热心人士开发了各平台的GUI版本的proxy,下面分平台介绍.
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
- 官方java版本,项目主页:[goproxy-jui](https://github.com/snail007/goproxy-jui)
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
- 官方java版本,项目主页:[goproxy-jui](https://github.com/snail007/goproxy-jui)
|
||||||
|
|
||||||
|
## MacOS
|
||||||
|
|
||||||
|
- Coming Soon ...
|
||||||
|
|
||||||
|
## Android
|
||||||
|
|
||||||
|
- proxy-go,一个非官方实现版本,界面比较简陋,但是够用.下载地址:[proxy-go](https://github.com/snail007/goproxy-gui-stuff/releases/tag/proxy-go-release)
|
||||||
|
|
||||||
|
|
||||||
|
## IOS
|
||||||
|
|
||||||
|
- Coming Soon ...
|
||||||
|
|
||||||
|
## 跨平台
|
||||||
|
|
||||||
|
- proxy-web,一个跨平台的web UI版本,项目主页:[proxy-web](https://github.com/yincongcyincong/proxy-web)
|
||||||
@ -1,12 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# install monexec
|
|
||||||
tar zxvf monexec_0.1.1_linux_amd64.tar.gz
|
|
||||||
cd monexec_0.1.1_linux_amd64
|
|
||||||
cp monexec /usr/bin/
|
|
||||||
chmod +x /usr/bin/monexec
|
|
||||||
cd ..
|
|
||||||
# #install proxy
|
# #install proxy
|
||||||
tar zxvf proxy-linux-amd64.tar.gz
|
tar zxvf proxy-linux-amd64.tar.gz
|
||||||
cp proxy /usr/bin/
|
cp proxy /usr/bin/
|
||||||
|
|||||||
@ -5,15 +5,8 @@ if [ -e /tmp/proxy ]; then
|
|||||||
fi
|
fi
|
||||||
mkdir /tmp/proxy
|
mkdir /tmp/proxy
|
||||||
cd /tmp/proxy
|
cd /tmp/proxy
|
||||||
wget https://github.com/reddec/monexec/releases/download/v0.1.1/monexec_0.1.1_linux_amd64.tar.gz
|
wget https://github.com/snail007/goproxy/releases/download/v4.9/proxy-linux-amd64.tar.gz
|
||||||
wget https://github.com/snail007/goproxy/releases/download/v3.6/proxy-linux-amd64.tar.gz
|
|
||||||
|
|
||||||
# install monexec
|
|
||||||
tar zxvf monexec_0.1.1_linux_amd64.tar.gz
|
|
||||||
cd monexec_0.1.1_linux_amd64
|
|
||||||
cp monexec /usr/bin/
|
|
||||||
chmod +x /usr/bin/monexec
|
|
||||||
cd ..
|
|
||||||
# #install proxy
|
# #install proxy
|
||||||
tar zxvf proxy-linux-amd64.tar.gz
|
tar zxvf proxy-linux-amd64.tar.gz
|
||||||
cp proxy /usr/bin/
|
cp proxy /usr/bin/
|
||||||
|
|||||||
25
main.go
@ -1,22 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"proxy/services"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APP_VERSION = "3.6"
|
const APP_VERSION = "4.9"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := initConfig()
|
err := initConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("err : %s", err)
|
log.Fatalf("err : %s", err)
|
||||||
}
|
}
|
||||||
Clean(&service.S)
|
if service != nil && service.S != nil {
|
||||||
|
Clean(&service.S)
|
||||||
|
} else {
|
||||||
|
Clean(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func Clean(s *services.Service) {
|
func Clean(s *services.Service) {
|
||||||
signalChan := make(chan os.Signal, 1)
|
signalChan := make(chan os.Signal, 1)
|
||||||
@ -29,8 +33,17 @@ func Clean(s *services.Service) {
|
|||||||
syscall.SIGQUIT)
|
syscall.SIGQUIT)
|
||||||
go func() {
|
go func() {
|
||||||
for _ = range signalChan {
|
for _ = range signalChan {
|
||||||
fmt.Println("\nReceived an interrupt, stopping services...")
|
log.Println("Received an interrupt, stopping services...")
|
||||||
(*s).Clean()
|
if s != nil && *s != nil {
|
||||||
|
(*s).Clean()
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
log.Printf("clean process %d", cmd.Process.Pid)
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
if isDebug {
|
||||||
|
saveProfiling()
|
||||||
|
}
|
||||||
cleanupDone <- true
|
cleanupDone <- true
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
91
release.sh
@ -1,69 +1,74 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
VER="3.6"
|
VER="4.9"
|
||||||
RELEASE="release-${VER}"
|
RELEASE="release-${VER}"
|
||||||
rm -rf .cert
|
rm -rf .cert
|
||||||
mkdir .cert
|
mkdir .cert
|
||||||
go build
|
go build -o proxy
|
||||||
cd .cert
|
cd .cert
|
||||||
../proxy keygen
|
../proxy keygen
|
||||||
cd ..
|
cd ..
|
||||||
rm -rf ${RELEASE}
|
rm -rf ${RELEASE}
|
||||||
mkdir ${RELEASE}
|
mkdir ${RELEASE}
|
||||||
export CGO_ENABLED=0
|
|
||||||
#linux
|
#linux
|
||||||
GOOS=linux GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-linux-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-386.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-linux-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=arm GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v6.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=arm64 GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=6 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v6.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=mips go build && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v7.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=mips64 go build && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=7 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v7.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=mips64le go build && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm-v5.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=mipsle go build && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=5 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-arm64-v5.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=ppc64 go build && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=ppc64le go build && tar zcfv "${RELEASE}/proxy-linux-ppc64le.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy direct blocked
|
||||||
GOOS=linux GOARCH=s390x go build && tar zcfv "${RELEASE}/proxy-linux-s390x.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-ppc64le.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-s390x.tar.gz" proxy direct blocked
|
||||||
#android
|
#android
|
||||||
GOOS=android GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-android-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=android GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-android-386.tar.gz" proxy direct blocked
|
||||||
GOOS=android GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-android-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=android GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-android-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=android GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-android-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=android GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-android-arm.tar.gz" proxy direct blocked
|
||||||
GOOS=android GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-android-arm64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=android GOARCH=arm64 go build -o proxy && tar zcfv "${RELEASE}/proxy-android-arm64.tar.gz" proxy direct blocked
|
||||||
#darwin
|
#darwin
|
||||||
GOOS=darwin GOARCH=386 go build go build && tar zcfv "${RELEASE}/proxy-darwin-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=darwin GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-darwin-386.tar.gz" proxy direct blocked
|
||||||
GOOS=darwin GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-darwin-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-darwin-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=darwin GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-darwin-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-darwin-arm.tar.gz" proxy direct blocked
|
||||||
GOOS=darwin GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-darwin-arm64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o proxy && tar zcfv "${RELEASE}/proxy-darwin-arm64.tar.gz" proxy direct blocked
|
||||||
#dragonfly
|
#dragonfly
|
||||||
GOOS=dragonfly GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-dragonfly-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-dragonfly-amd64.tar.gz" proxy direct blocked
|
||||||
#freebsd
|
#freebsd
|
||||||
GOOS=freebsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-freebsd-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-386.tar.gz" proxy direct blocked
|
||||||
GOOS=freebsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-freebsd-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=freebsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-freebsd-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-arm.tar.gz" proxy direct blocked
|
||||||
#nacl
|
#nacl
|
||||||
GOOS=nacl GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-nacl-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=nacl GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-nacl-386.tar.gz" proxy direct blocked
|
||||||
GOOS=nacl GOARCH=amd64p32 go build && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=nacl GOARCH=amd64p32 go build -o proxy && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.tar.gz" proxy direct blocked
|
||||||
GOOS=nacl GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-nacl-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=nacl GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-nacl-arm.tar.gz" proxy direct blocked
|
||||||
#netbsd
|
#netbsd
|
||||||
GOOS=netbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-netbsd-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-386.tar.gz" proxy direct blocked
|
||||||
GOOS=netbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-netbsd-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=netbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-netbsd-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-arm.tar.gz" proxy direct blocked
|
||||||
#openbsd
|
#openbsd
|
||||||
GOOS=openbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-openbsd-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=openbsd GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-386.tar.gz" proxy direct blocked
|
||||||
GOOS=openbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-openbsd-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=openbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-openbsd-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-arm.tar.gz" proxy direct blocked
|
||||||
#plan9
|
#plan9
|
||||||
GOOS=plan9 GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-plan9-386.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=plan9 GOARCH=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-plan9-386.tar.gz" proxy direct blocked
|
||||||
GOOS=plan9 GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-plan9-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=plan9 GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-plan9-amd64.tar.gz" proxy direct blocked
|
||||||
GOOS=plan9 GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-plan9-arm.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=plan9 GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-plan9-arm.tar.gz" proxy direct blocked
|
||||||
#solaris
|
#solaris
|
||||||
GOOS=solaris GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-solaris-amd64.tar.gz" proxy direct blocked
|
CGO_ENABLED=0 GOOS=solaris GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-solaris-amd64.tar.gz" proxy direct blocked
|
||||||
#windows
|
#windows
|
||||||
GOOS=windows GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags="-H=windowsgui" -o proxy-wingui.exe
|
||||||
GOOS=windows GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-windows-amd64.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o proxy.exe && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe proxy-wingui.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-H=windowsgui" -o proxy-wingui.exe
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o proxy.exe && tar zcfv "${RELEASE}/proxy-windows-amd64.tar.gz" proxy.exe proxy-wingui.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||||
|
|
||||||
rm -rf proxy proxy.exe .cert
|
rm -rf proxy proxy.exe proxy-wingui.exe .cert
|
||||||
|
|
||||||
#todo
|
#todo
|
||||||
#1.release.sh VER="xxx"
|
#1.release.sh VER="xxx"
|
||||||
#2.main.go APP_VERSION="xxx"
|
#2.main.go APP_VERSION="xxx"
|
||||||
#3.install_auto.sh goproxy/releases/download/vxxx
|
#3.install_auto.sh goproxy/releases/download/vxxx
|
||||||
#4.README goproxy/releases/download/vxxx
|
#4.README goproxy/releases/download/vxxx
|
||||||
|
|||||||
3
sdk/CHANGELOG
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
v4.8
|
||||||
|
1.修复了多个服务同时开启日志,只会输出到最后一个日志文件的bug.
|
||||||
|
2.增加了获取sdk版本的Version()方法.
|
||||||
259
sdk/README.md
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
|
||||||
|
# Proxy SDK 使用说明
|
||||||
|
|
||||||
|
支持以下平台:
|
||||||
|
- Android,`.arr`库
|
||||||
|
- IOS,`.framework`库
|
||||||
|
- Windows,`.dll`库
|
||||||
|
- Linux,`.so`库
|
||||||
|
- MacOS,`.dylib`库
|
||||||
|
|
||||||
|
proxy使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库,
|
||||||
|
另外还为linux和windows,MacOS提供sdk支持,基于这些类库,APP开发者可以轻松的开发出各种形式的代理工具。
|
||||||
|
|
||||||
|
# 下面分平台介绍SDK的用法
|
||||||
|
|
||||||
|
## Android SDK
|
||||||
|
|
||||||
|
[](https://github.com/snail007/goproxy-sdk-android/) []() [](https://github.com/snail007/goproxy-sdk-android/releases) [](https://github.com/snail007/goproxy-sdk-android/releases)
|
||||||
|
|
||||||
|
[点击下载Android-SDK](https://github.com/snail007/goproxy-sdk-android/releases)
|
||||||
|
在Android系统提供的sdk形式是一个后缀为.aar的类库文件,开发的时候只需要把arr类库文件引入android项目即可.
|
||||||
|
|
||||||
|
### Android-SDK使用实例
|
||||||
|
|
||||||
|
#### 1.导入包
|
||||||
|
|
||||||
|
```java
|
||||||
|
import snail007.proxy.Porxy
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.启动一个服务
|
||||||
|
|
||||||
|
```java
|
||||||
|
String serviceID="http01";//这里serviceID是自定义的唯一标识字符串,保证每个启动的服务不一样即可
|
||||||
|
String serviceArgs="http -p :8080";
|
||||||
|
String err=Proxy.start(serviceID,serviceArgs);
|
||||||
|
if (!err.isEmpty()){
|
||||||
|
//启动失败
|
||||||
|
System.out.println("start fail,error:"+err);
|
||||||
|
}else{
|
||||||
|
//启动成功
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.停止一个服务
|
||||||
|
|
||||||
|
```java
|
||||||
|
String serviceID="http01";
|
||||||
|
Proxy.stop(serviceID);
|
||||||
|
//停止完毕
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## IOS SDK
|
||||||
|
|
||||||
|
[](https://github.com/snail007/goproxy-sdk-ios/) []() [](https://github.com/snail007/goproxy-sdk-ios/releases) [](https://github.com/snail007/goproxy-sdk-ios/releases)
|
||||||
|
|
||||||
|
[点击下载IOS-SDK](https://github.com/snail007/goproxy-sdk-ios/releases)
|
||||||
|
在IOS系统提供的sdk形式是一个后缀为.framework的类库文件夹,开发的时候只需要把类库文件引入项目,然后调用方法即可.
|
||||||
|
|
||||||
|
### IOS-SDK使用实例
|
||||||
|
|
||||||
|
#### 导入包
|
||||||
|
|
||||||
|
```objc
|
||||||
|
#import <Proxy/Proxy.objc.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.启动一个服务
|
||||||
|
|
||||||
|
```objc
|
||||||
|
-(IBAction)doStart:(id)sender
|
||||||
|
{
|
||||||
|
//这里serviceID是自定义的唯一标识字符串,保证每个启动的服务不一样
|
||||||
|
NSString *serviceID = @"http01";
|
||||||
|
NSString *serviceArgs = @"http -p :8080";
|
||||||
|
NSString *error = ProxyStart(serviceID,serviceArgs);
|
||||||
|
|
||||||
|
if (error != nil && error.length > 0)
|
||||||
|
{
|
||||||
|
NSLog(@"start error %@",error);
|
||||||
|
}else{
|
||||||
|
NSLog(@"启动成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.停止一个服务
|
||||||
|
|
||||||
|
```objc
|
||||||
|
-(IBAction)doStop:(id)sender
|
||||||
|
{
|
||||||
|
NSString *serviceID = @"http01";
|
||||||
|
ProxyStop(serviceID);
|
||||||
|
//停止完毕
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Windows SDK
|
||||||
|
[](https://github.com/snail007/goproxy-sdk-windows/) []() [](https://github.com/snail007/goproxy-sdk-windows/releases) [](https://github.com/snail007/goproxy-sdk-windows/releases)
|
||||||
|
|
||||||
|
[点击下载Windows-SDK](https://github.com/snail007/goproxy-sdk-windows/releases)
|
||||||
|
在Windows系统提供的sdk形式是一个后缀为.dll的类库文件,开发的时候只需要把dll类库文件加载,然后调用方法即可.
|
||||||
|
|
||||||
|
### Windows-SDK使用实例
|
||||||
|
C++示例,不需要包含头文件,只需要加载proxy-sdk.dll即可,ieshims.dll需要和proxy-sdk.dll在一起。
|
||||||
|
作者:[yjbdsky](https://github.com/yjbdsky)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <stdio.h>
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include<pthread.h>
|
||||||
|
#include<Windows.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef char *(*GOSTART)(char *s);
|
||||||
|
typedef char *(*GOSTOP)(char *s);
|
||||||
|
typedef int(*GOISRUN)(char *s);
|
||||||
|
HMODULE GODLL = LoadLibrary("proxy-sdk.dll");
|
||||||
|
|
||||||
|
char * Start(char * p0,char * p1)
|
||||||
|
{
|
||||||
|
if (GODLL != NULL)
|
||||||
|
{
|
||||||
|
GOSTART gostart = *(GOSTART)(GetProcAddress(GODLL, "Start"));
|
||||||
|
if (gostart != NULL){
|
||||||
|
printf("%s:%s\n",p0, p1);
|
||||||
|
char *ret = gostart(p0,p1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Cannot Find dll";
|
||||||
|
}
|
||||||
|
char * Stop(char * p)
|
||||||
|
{
|
||||||
|
if (GODLL != NULL)
|
||||||
|
{
|
||||||
|
GOSTOP gostop = *(GOSTOP)(GetProcAddress(GODLL, "Stop"));
|
||||||
|
if (gostop != NULL){
|
||||||
|
printf("%s\n", p);
|
||||||
|
char *ret = gostop(p);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Cannot Find dll";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
//这里p0是自定义的唯一标识字符串,保证每个启动的服务不一样
|
||||||
|
char *p0 = "http01";
|
||||||
|
char *p1 = "http -t tcp -p :38080";
|
||||||
|
printf("This is demo application.\n");
|
||||||
|
//启动服务,返回空字符串说明启动成功;返回非空字符串说明启动失败,返回的字符串是错误原因
|
||||||
|
printf("start result %s\n", Start(p0,p1));
|
||||||
|
//停止服务,没有返回值
|
||||||
|
Stop(p0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
C++示例2,请移步:[GoProxyForC](https://github.com/SuperPowerLF2/GoProxyForC)
|
||||||
|
|
||||||
|
## Linux SDK
|
||||||
|
[](https://github.com/snail007/goproxy-sdk-linux/) []() [](https://github.com/snail007/goproxy-sdk-linux/releases) [](https://github.com/snail007/goproxy-sdk-linux/releases)
|
||||||
|
|
||||||
|
[点击下载Linux-SDK](https://github.com/snail007/goproxy-sdk-linux/releases)
|
||||||
|
在Linux系统提供的sdk形式是一个后缀为.so的类库文件,开发的时候只需要把so类库加载,调用方法即可.
|
||||||
|
|
||||||
|
### Linux-SDK使用实例
|
||||||
|
Linux下面使用的sdk是so文件即libproxy-sdk.so,下面写一个简单的C程序示例,调用so库里面的方法.
|
||||||
|
|
||||||
|
`vi test-proxy.c`
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "libproxy-sdk.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("This is demo application.\n");
|
||||||
|
//这里p0是自定义的唯一标识字符串,保证每个启动的服务不一样
|
||||||
|
char *p0 = "http01";
|
||||||
|
char *p1 = "http -t tcp -p :38080";
|
||||||
|
//启动服务,返回空字符串说明启动成功;返回非空字符串说明启动失败,返回的字符串是错误原因
|
||||||
|
printf("start result %s\n",Start(p0,p1));
|
||||||
|
//停止服务,没有返回值
|
||||||
|
Stop(p0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编译test-proxy.c ####
|
||||||
|
`export LD_LIBRARY_PATH=./ && gcc -o test-proxy test.c libproxy-sdk.so`
|
||||||
|
|
||||||
|
#### 执行 ####
|
||||||
|
`./test-proxy`
|
||||||
|
|
||||||
|
## MacOS SDK
|
||||||
|
[](https://github.com/snail007/goproxy-sdk-mac/) []() [](https://github.com/snail007/goproxy-sdk-mac/releases) [](https://github.com/snail007/goproxy-sdk-mac/releases)
|
||||||
|
|
||||||
|
[点击下载MacOS-SDK](https://github.com/snail007/goproxy-sdk-mac/releases)
|
||||||
|
在MacOS系统提供的sdk形式是一个后缀为.dylib的类库文件,开发的时候只需要把so类库加载,调用方法即可.
|
||||||
|
|
||||||
|
### MacOS-SDK使用实例
|
||||||
|
MacOS下面使用的sdk是dylib文件即libproxy-sdk.dylib,下面写一个简单的Obj-C程序示例,调用dylib库里面的方法.
|
||||||
|
|
||||||
|
```objc
|
||||||
|
#import "libproxy-sdk.h"
|
||||||
|
-(IBAction)doStart:(id)sender
|
||||||
|
{
|
||||||
|
char *result = Start("http01", "http -t tcp -p :38080");
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
printf("started");
|
||||||
|
}else{
|
||||||
|
printf("not started");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
-(IBAction)doStop:(id)sender
|
||||||
|
{
|
||||||
|
Stop("http01");
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关于服务
|
||||||
|
proxy的服务有11种,分别是:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
http
|
||||||
|
socks
|
||||||
|
sps
|
||||||
|
tcp
|
||||||
|
udp
|
||||||
|
bridge
|
||||||
|
server
|
||||||
|
client
|
||||||
|
tbridge
|
||||||
|
tserver
|
||||||
|
tclient
|
||||||
|
```
|
||||||
|
服务启动时,如果存在正在运行的相同ID的服务,那么之前的服务会被停掉,后面启动的服务覆盖之前的服务.
|
||||||
|
所以要保证每次启动服务的时候,第一个ID参数唯一.
|
||||||
|
上面这些服务的具体使用方式和具体参数,可以参考[proxy手册](https://github.com/snail007/goproxy/blob/master/README_ZH.md)
|
||||||
|
sdk里面的服务不支持手册里面的:--daemon和--forever参数.
|
||||||
|
|
||||||
|
|
||||||
7
sdk/android-ios/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
*.jar
|
||||||
|
*.aar
|
||||||
|
*.tar.gz
|
||||||
|
ios
|
||||||
|
android
|
||||||
|
Proxy.framework
|
||||||
|
|
||||||
24
sdk/android-ios/release_android.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#/bin/bash
|
||||||
|
VER="v4.8"
|
||||||
|
rm -rf sdk-android-*.tar.gz
|
||||||
|
rm -rf android
|
||||||
|
mkdir android
|
||||||
|
|
||||||
|
#android ; jdk,android ndk & android sdk required, install gomobile go1.10 required
|
||||||
|
#export GOPATH="$HOME/go"
|
||||||
|
#export GOROOT="/usr/local/go"
|
||||||
|
#export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"
|
||||||
|
#export ANDROID_HOME="$HOME/Android/Sdk"
|
||||||
|
#export NDK_ROOT="$HOME/Android/Sdk/ndk-bundle"
|
||||||
|
#export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$NDK_ROOT:$PATH"
|
||||||
|
#go get -v golang.org/x/mobile/cmd/gomobile
|
||||||
|
#gomobile init
|
||||||
|
|
||||||
|
gomobile bind -v -target=android -javapkg=snail007 -ldflags="-s -w"
|
||||||
|
mv proxy.aar android/snail007.goproxy.sdk.aar
|
||||||
|
mv proxy-sources.jar android/snail007.goproxy.sdk-sources.jar
|
||||||
|
cp ../README.md android
|
||||||
|
tar zcfv sdk-android-${VER}.tar.gz android
|
||||||
|
rm -rf android
|
||||||
|
|
||||||
|
echo "done."
|
||||||
14
sdk/android-ios/release_ios.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#/bin/bash
|
||||||
|
VER="v4.8"
|
||||||
|
rm -rf sdk-ios-*.tar.gz
|
||||||
|
rm -rf ios
|
||||||
|
mkdir ios
|
||||||
|
|
||||||
|
#ios XCode required
|
||||||
|
gomobile bind -v -target=ios -ldflags="-s -w"
|
||||||
|
mv Proxy.framework ios
|
||||||
|
cp ../README.md ios
|
||||||
|
tar zcfv sdk-ios-${VER}.tar.gz ios
|
||||||
|
rm -rf ios
|
||||||
|
|
||||||
|
echo "done."
|
||||||
361
sdk/android-ios/sdk.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"fmt"
|
||||||
|
logger "log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services"
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
|
||||||
|
kcp "github.com/xtaci/kcp-go"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SDK_VERSION = "4.8"
|
||||||
|
|
||||||
|
var (
|
||||||
|
app *kingpin.Application
|
||||||
|
)
|
||||||
|
|
||||||
|
//Start
|
||||||
|
//serviceID : is service identify id,different service's id should be difference
|
||||||
|
//serviceArgsStr: is the whole command line args string
|
||||||
|
//such as :
|
||||||
|
//1."http -t tcp -p :8989"
|
||||||
|
//2."socks -t tcp -p :8989"
|
||||||
|
//and so on.
|
||||||
|
//if an error occured , errStr will be the error reason
|
||||||
|
//if start success, errStr is empty.
|
||||||
|
func Start(serviceID, serviceArgsStr string) (errStr string) {
|
||||||
|
//define args
|
||||||
|
tcpArgs := services.TCPArgs{}
|
||||||
|
httpArgs := services.HTTPArgs{}
|
||||||
|
tunnelServerArgs := services.TunnelServerArgs{}
|
||||||
|
tunnelClientArgs := services.TunnelClientArgs{}
|
||||||
|
tunnelBridgeArgs := services.TunnelBridgeArgs{}
|
||||||
|
muxServerArgs := services.MuxServerArgs{}
|
||||||
|
muxClientArgs := services.MuxClientArgs{}
|
||||||
|
muxBridgeArgs := services.MuxBridgeArgs{}
|
||||||
|
udpArgs := services.UDPArgs{}
|
||||||
|
socksArgs := services.SocksArgs{}
|
||||||
|
spsArgs := services.SPSArgs{}
|
||||||
|
kcpArgs := kcpcfg.KCPConfigArgs{}
|
||||||
|
//build srvice args
|
||||||
|
app = kingpin.New("proxy", "happy with proxy")
|
||||||
|
app.Author("snail").Version(SDK_VERSION)
|
||||||
|
debug := app.Flag("debug", "debug log output").Default("false").Bool()
|
||||||
|
logfile := app.Flag("log", "log file path").Default("").String()
|
||||||
|
kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String()
|
||||||
|
kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none")
|
||||||
|
kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast3").Enum("fast3", "fast2", "fast", "normal", "manual")
|
||||||
|
kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int()
|
||||||
|
kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int()
|
||||||
|
kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int()
|
||||||
|
kcpArgs.DataShard = app.Flag("kcp-ds", "set reed-solomon erasure coding - datashard").Default("10").Int()
|
||||||
|
kcpArgs.ParityShard = app.Flag("kcp-ps", "set reed-solomon erasure coding - parityshard").Default("3").Int()
|
||||||
|
kcpArgs.DSCP = app.Flag("kcp-dscp", "set DSCP(6bit)").Default("0").Int()
|
||||||
|
kcpArgs.NoComp = app.Flag("kcp-nocomp", "disable compression").Default("false").Bool()
|
||||||
|
kcpArgs.AckNodelay = app.Flag("kcp-acknodelay", "be carefull! flush ack immediately when a packet is received").Default("true").Bool()
|
||||||
|
kcpArgs.NoDelay = app.Flag("kcp-nodelay", "be carefull!").Default("0").Int()
|
||||||
|
kcpArgs.Interval = app.Flag("kcp-interval", "be carefull!").Default("50").Int()
|
||||||
|
kcpArgs.Resend = app.Flag("kcp-resend", "be carefull!").Default("0").Int()
|
||||||
|
kcpArgs.NoCongestion = app.Flag("kcp-nc", "be carefull! no congestion").Default("0").Int()
|
||||||
|
kcpArgs.SockBuf = app.Flag("kcp-sockbuf", "be carefull!").Default("4194304").Int()
|
||||||
|
kcpArgs.KeepAlive = app.Flag("kcp-keepalive", "be carefull!").Default("10").Int()
|
||||||
|
|
||||||
|
//########http#########
|
||||||
|
http := app.Command("http", "proxy on http mode")
|
||||||
|
httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
httpArgs.CaCertFile = http.Flag("ca", "ca cert file for tls").Default("").String()
|
||||||
|
httpArgs.CertFile = http.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
httpArgs.KeyFile = http.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
httpArgs.LocalType = http.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
httpArgs.ParentType = http.Flag("parent-type", "parent protocol type <tls|tcp|ssh|kcp>").Short('T').Enum("tls", "tcp", "ssh", "kcp")
|
||||||
|
httpArgs.Always = http.Flag("always", "always use parent proxy").Default("false").Bool()
|
||||||
|
httpArgs.Timeout = http.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int()
|
||||||
|
httpArgs.HTTPTimeout = http.Flag("http-timeout", "check domain if blocked , http request timeout milliseconds when connect to host").Default("3000").Int()
|
||||||
|
httpArgs.Interval = http.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int()
|
||||||
|
httpArgs.Blocked = http.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String()
|
||||||
|
httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||||
|
httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||||
|
httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||||
|
httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||||
|
httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||||
|
httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||||
|
httpArgs.SSHKeyFile = http.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||||
|
httpArgs.SSHKeyFileSalt = http.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||||
|
httpArgs.SSHPassword = http.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
||||||
|
httpArgs.LocalIPS = http.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||||
|
httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||||
|
httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||||
|
httpArgs.AuthURLOkCode = http.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||||
|
httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int()
|
||||||
|
httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||||
|
httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||||
|
httpArgs.LocalKey = http.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||||
|
httpArgs.ParentKey = http.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||||
|
httpArgs.LocalCompress = http.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||||
|
httpArgs.ParentCompress = http.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||||
|
|
||||||
|
//########tcp#########
|
||||||
|
tcp := app.Command("tcp", "proxy on tcp mode")
|
||||||
|
tcpArgs.Parent = tcp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
tcpArgs.CertFile = tcp.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
tcpArgs.KeyFile = tcp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int()
|
||||||
|
tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type <tls|tcp|kcp|udp>").Short('T').Enum("tls", "tcp", "udp", "kcp")
|
||||||
|
tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||||
|
tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
|
||||||
|
//########udp#########
|
||||||
|
udp := app.Command("udp", "proxy on udp mode")
|
||||||
|
udpArgs.Parent = udp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
udpArgs.CertFile = udp.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int()
|
||||||
|
udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type <tls|tcp|udp>").Short('T').Enum("tls", "tcp", "udp")
|
||||||
|
udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int()
|
||||||
|
udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
|
||||||
|
//########mux-server#########
|
||||||
|
muxServer := app.Command("server", "proxy on mux server mode")
|
||||||
|
muxServerArgs.Parent = muxServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
muxServerArgs.ParentType = muxServer.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Default("tls").Short('T').Enum("tls", "tcp", "kcp")
|
||||||
|
muxServerArgs.CertFile = muxServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
muxServerArgs.KeyFile = muxServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||||
|
muxServerArgs.IsUDP = muxServer.Flag("udp", "proxy on udp mux server mode").Default("false").Bool()
|
||||||
|
muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String()
|
||||||
|
muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||||
|
muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||||
|
muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||||
|
|
||||||
|
//########mux-client#########
|
||||||
|
muxClient := app.Command("client", "proxy on mux client mode")
|
||||||
|
muxClientArgs.Parent = muxClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
muxClientArgs.ParentType = muxClient.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Default("tls").Short('T').Enum("tls", "tcp", "kcp")
|
||||||
|
muxClientArgs.CertFile = muxClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
muxClientArgs.KeyFile = muxClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||||
|
muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String()
|
||||||
|
muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp|tls mode").Default("false").Bool()
|
||||||
|
muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int()
|
||||||
|
|
||||||
|
//########mux-bridge#########
|
||||||
|
muxBridge := app.Command("bridge", "proxy on mux bridge mode")
|
||||||
|
muxBridgeArgs.CertFile = muxBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
muxBridgeArgs.KeyFile = muxBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int()
|
||||||
|
muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
muxBridgeArgs.LocalType = muxBridge.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tls").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
|
||||||
|
//########tunnel-server#########
|
||||||
|
tunnelServer := app.Command("tserver", "proxy on tunnel server mode")
|
||||||
|
tunnelServerArgs.Parent = tunnelServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
tunnelServerArgs.CertFile = tunnelServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
tunnelServerArgs.KeyFile = tunnelServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
tunnelServerArgs.Timeout = tunnelServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||||
|
tunnelServerArgs.IsUDP = tunnelServer.Flag("udp", "proxy on udp tunnel server mode").Default("false").Bool()
|
||||||
|
tunnelServerArgs.Key = tunnelServer.Flag("k", "client key").Default("default").String()
|
||||||
|
tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings()
|
||||||
|
|
||||||
|
//########tunnel-client#########
|
||||||
|
tunnelClient := app.Command("tclient", "proxy on tunnel client mode")
|
||||||
|
tunnelClientArgs.Parent = tunnelClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
tunnelClientArgs.CertFile = tunnelClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
tunnelClientArgs.KeyFile = tunnelClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
tunnelClientArgs.Timeout = tunnelClient.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||||
|
tunnelClientArgs.Key = tunnelClient.Flag("k", "key same with server").Default("default").String()
|
||||||
|
|
||||||
|
//########tunnel-bridge#########
|
||||||
|
tunnelBridge := app.Command("tbridge", "proxy on tunnel bridge mode")
|
||||||
|
tunnelBridgeArgs.CertFile = tunnelBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
tunnelBridgeArgs.KeyFile = tunnelBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
tunnelBridgeArgs.Timeout = tunnelBridge.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int()
|
||||||
|
tunnelBridgeArgs.Local = tunnelBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
|
||||||
|
//########ssh#########
|
||||||
|
socks := app.Command("socks", "proxy on ssh mode")
|
||||||
|
socksArgs.Parent = socks.Flag("parent", "parent ssh address, such as: \"23.32.32.19:22\"").Default("").Short('P').String()
|
||||||
|
socksArgs.ParentType = socks.Flag("parent-type", "parent protocol type <tls|tcp|kcp|ssh>").Default("tcp").Short('T').Enum("tls", "tcp", "kcp", "ssh")
|
||||||
|
socksArgs.LocalType = socks.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
socksArgs.Local = socks.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
socksArgs.UDPParent = socks.Flag("udp-parent", "udp parent address, such as: \"23.32.32.19:33090\"").Default("").Short('X').String()
|
||||||
|
socksArgs.UDPLocal = socks.Flag("udp-local", "udp local ip:port to listen").Short('x').Default(":33090").String()
|
||||||
|
socksArgs.CertFile = socks.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
socksArgs.CaCertFile = socks.Flag("ca", "ca cert file for tls").Default("").String()
|
||||||
|
socksArgs.KeyFile = socks.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
socksArgs.SSHUser = socks.Flag("ssh-user", "user for ssh").Short('u').Default("").String()
|
||||||
|
socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String()
|
||||||
|
socksArgs.SSHKeyFileSalt = socks.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String()
|
||||||
|
socksArgs.SSHPassword = socks.Flag("ssh-password", "password for ssh").Short('A').Default("").String()
|
||||||
|
socksArgs.Always = socks.Flag("always", "always use parent proxy").Default("false").Bool()
|
||||||
|
socksArgs.Timeout = socks.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("5000").Int()
|
||||||
|
socksArgs.Interval = socks.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int()
|
||||||
|
socksArgs.Blocked = socks.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String()
|
||||||
|
socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String()
|
||||||
|
socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||||
|
socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||||
|
socksArgs.LocalIPS = socks.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||||
|
socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||||
|
socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||||
|
socksArgs.AuthURLOkCode = socks.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||||
|
socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||||
|
socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||||
|
socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||||
|
socksArgs.LocalKey = socks.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||||
|
socksArgs.ParentKey = socks.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||||
|
socksArgs.LocalCompress = socks.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||||
|
socksArgs.ParentCompress = socks.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||||
|
|
||||||
|
//########socks+http(s)#########
|
||||||
|
sps := app.Command("sps", "proxy on socks+http(s) mode")
|
||||||
|
spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String()
|
||||||
|
spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String()
|
||||||
|
spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String()
|
||||||
|
spsArgs.CaCertFile = sps.Flag("ca", "ca cert file for tls").Default("").String()
|
||||||
|
spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int()
|
||||||
|
spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type <tls|tcp|kcp>").Short('T').Enum("tls", "tcp", "kcp")
|
||||||
|
spsArgs.LocalType = sps.Flag("local-type", "local protocol type <tls|tcp|kcp>").Default("tcp").Short('t').Enum("tls", "tcp", "kcp")
|
||||||
|
spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String()
|
||||||
|
spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type <http|socks>").Short('S').Enum("http", "socks")
|
||||||
|
spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String()
|
||||||
|
spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int()
|
||||||
|
spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String()
|
||||||
|
spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings()
|
||||||
|
spsArgs.LocalIPS = sps.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings()
|
||||||
|
spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String()
|
||||||
|
spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int()
|
||||||
|
spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int()
|
||||||
|
spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int()
|
||||||
|
spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String()
|
||||||
|
spsArgs.LocalKey = sps.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String()
|
||||||
|
spsArgs.ParentKey = sps.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
|
||||||
|
spsArgs.LocalCompress = sps.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
|
||||||
|
spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
|
||||||
|
spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool()
|
||||||
|
spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool()
|
||||||
|
|
||||||
|
//parse args
|
||||||
|
_args := strings.Fields(strings.Trim(serviceArgsStr, " "))
|
||||||
|
args := []string{}
|
||||||
|
for _, a := range _args {
|
||||||
|
args = append(args, strings.Trim(a, "\""))
|
||||||
|
}
|
||||||
|
serviceName, err := app.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("parse args fail,err: %s", err)
|
||||||
|
}
|
||||||
|
//set kcp config
|
||||||
|
|
||||||
|
switch *kcpArgs.Mode {
|
||||||
|
case "normal":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 40, 2, 1
|
||||||
|
case "fast":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 30, 2, 1
|
||||||
|
case "fast2":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 20, 2, 1
|
||||||
|
case "fast3":
|
||||||
|
*kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 10, 2, 1
|
||||||
|
}
|
||||||
|
pass := pbkdf2.Key([]byte(*kcpArgs.Key), []byte("snail007-goproxy"), 4096, 32, sha1.New)
|
||||||
|
|
||||||
|
switch *kcpArgs.Crypt {
|
||||||
|
case "sm4":
|
||||||
|
kcpArgs.Block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||||
|
case "tea":
|
||||||
|
kcpArgs.Block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||||
|
case "xor":
|
||||||
|
kcpArgs.Block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||||
|
case "none":
|
||||||
|
kcpArgs.Block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||||
|
case "aes-128":
|
||||||
|
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||||
|
case "aes-192":
|
||||||
|
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||||
|
case "blowfish":
|
||||||
|
kcpArgs.Block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||||
|
case "twofish":
|
||||||
|
kcpArgs.Block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||||
|
case "cast5":
|
||||||
|
kcpArgs.Block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||||
|
case "3des":
|
||||||
|
kcpArgs.Block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||||
|
case "xtea":
|
||||||
|
kcpArgs.Block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||||
|
case "salsa20":
|
||||||
|
kcpArgs.Block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||||
|
default:
|
||||||
|
*kcpArgs.Crypt = "aes"
|
||||||
|
kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass)
|
||||||
|
}
|
||||||
|
//attach kcp config
|
||||||
|
tcpArgs.KCP = kcpArgs
|
||||||
|
httpArgs.KCP = kcpArgs
|
||||||
|
socksArgs.KCP = kcpArgs
|
||||||
|
spsArgs.KCP = kcpArgs
|
||||||
|
muxBridgeArgs.KCP = kcpArgs
|
||||||
|
muxServerArgs.KCP = kcpArgs
|
||||||
|
muxClientArgs.KCP = kcpArgs
|
||||||
|
|
||||||
|
log := logger.New(os.Stderr, "", logger.Ldate|logger.Ltime)
|
||||||
|
flags := logger.Ldate
|
||||||
|
if *debug {
|
||||||
|
flags |= logger.Lshortfile | logger.Lmicroseconds
|
||||||
|
} else {
|
||||||
|
flags |= logger.Ltime
|
||||||
|
}
|
||||||
|
log.SetFlags(flags)
|
||||||
|
|
||||||
|
if *logfile != "" {
|
||||||
|
f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||||
|
if e != nil {
|
||||||
|
log.Fatal(e)
|
||||||
|
}
|
||||||
|
log.SetOutput(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
//regist services and run service
|
||||||
|
switch serviceName {
|
||||||
|
case "http":
|
||||||
|
services.Regist(serviceID, services.NewHTTP(), httpArgs, log)
|
||||||
|
case "tcp":
|
||||||
|
services.Regist(serviceID, services.NewTCP(), tcpArgs, log)
|
||||||
|
case "udp":
|
||||||
|
services.Regist(serviceID, services.NewUDP(), udpArgs, log)
|
||||||
|
case "tserver":
|
||||||
|
services.Regist(serviceID, services.NewTunnelServerManager(), tunnelServerArgs, log)
|
||||||
|
case "tclient":
|
||||||
|
services.Regist(serviceID, services.NewTunnelClient(), tunnelClientArgs, log)
|
||||||
|
case "tbridge":
|
||||||
|
services.Regist(serviceID, services.NewTunnelBridge(), tunnelBridgeArgs, log)
|
||||||
|
case "server":
|
||||||
|
services.Regist(serviceID, services.NewMuxServerManager(), muxServerArgs, log)
|
||||||
|
case "client":
|
||||||
|
services.Regist(serviceID, services.NewMuxClient(), muxClientArgs, log)
|
||||||
|
case "bridge":
|
||||||
|
services.Regist(serviceID, services.NewMuxBridge(), muxBridgeArgs, log)
|
||||||
|
case "socks":
|
||||||
|
services.Regist(serviceID, services.NewSocks(), socksArgs, log)
|
||||||
|
case "sps":
|
||||||
|
services.Regist(serviceID, services.NewSPS(), spsArgs, log)
|
||||||
|
}
|
||||||
|
_, err = services.Run(serviceID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("run service [%s:%s] fail, ERR:%s", serviceID, serviceName, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop(serviceID string) {
|
||||||
|
services.Stop(serviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() string {
|
||||||
|
return SDK_VERSION
|
||||||
|
}
|
||||||
6
sdk/windows-linux/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
proxy-sdk.dll
|
||||||
|
proxy-sdk.h
|
||||||
|
proxy-sdk.so
|
||||||
|
proxy-sdk.a
|
||||||
|
*.tar.gz
|
||||||
|
test.c
|
||||||
BIN
sdk/windows-linux/ieshims.dll
Normal file
24
sdk/windows-linux/release_linux.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#/bin/bash
|
||||||
|
VER="v4.8"
|
||||||
|
|
||||||
|
rm -rf sdk-linux-*.tar.gz
|
||||||
|
rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a
|
||||||
|
|
||||||
|
#linux 32bit
|
||||||
|
CGO_ENABLED=1 GOARCH=386 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o libproxy-sdk.a sdk.go
|
||||||
|
CGO_ENABLED=1 GOARCH=386 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o libproxy-sdk.so sdk.go
|
||||||
|
cp ../README.md .
|
||||||
|
tar zcf sdk-linux-32bit-${VER}.tar.gz README.md libproxy-sdk.so libproxy-sdk.a libproxy-sdk.h
|
||||||
|
rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a
|
||||||
|
|
||||||
|
#linux 64bit
|
||||||
|
CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o libproxy-sdk.a sdk.go
|
||||||
|
CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o libproxy-sdk.so sdk.go
|
||||||
|
cp ../README.md .
|
||||||
|
tar zcf sdk-linux-64bit-${VER}.tar.gz README.md libproxy-sdk.so libproxy-sdk.a libproxy-sdk.h
|
||||||
|
rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a
|
||||||
|
|
||||||
|
echo "done."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
13
sdk/windows-linux/release_mac.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#/bin/bash
|
||||||
|
VER="v4.8"
|
||||||
|
|
||||||
|
rm -rf *.tar.gz
|
||||||
|
rm -rf README.md libproxy-sdk.dylib libproxy-sdk.h
|
||||||
|
|
||||||
|
#mac , macos required
|
||||||
|
CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin go build -buildmode=c-shared -ldflags "-s -w" -o libproxy-sdk.dylib sdk.go
|
||||||
|
cp ../README.md .
|
||||||
|
tar zcf sdk-mac-${VER}.tar.gz README.md libproxy-sdk.dylib libproxy-sdk.h
|
||||||
|
rm -rf README.md libproxy-sdk.dylib libproxy-sdk.h
|
||||||
|
|
||||||
|
echo "done."
|
||||||
28
sdk/windows-linux/release_windows.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#/bin/bash
|
||||||
|
VER="v4.8"
|
||||||
|
|
||||||
|
sudo rm /usr/local/go
|
||||||
|
sudo ln -s /usr/local/go1.10.1 /usr/local/go
|
||||||
|
rm -rf sdk-windows-*.tar.gz
|
||||||
|
rm -rf README.md proxy-sdk.h proxy-sdk.dll
|
||||||
|
|
||||||
|
|
||||||
|
#apt-get install gcc-multilib
|
||||||
|
#apt-get install gcc-mingw-w64
|
||||||
|
|
||||||
|
#windows 64bit
|
||||||
|
CC=x86_64-w64-mingw32-gcc GOARCH=amd64 CGO_ENABLED=1 GOOS=windows go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dll sdk.go
|
||||||
|
cp ../README.md .
|
||||||
|
tar zcf sdk-windows-64bit-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll
|
||||||
|
rm -rf README.md proxy-sdk.h proxy-sdk.dll
|
||||||
|
|
||||||
|
#windows 32bit
|
||||||
|
CC=i686-w64-mingw32-gcc-win32 GOARCH=386 CGO_ENABLED=1 GOOS=windows go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dll sdk.go
|
||||||
|
cp ../README.md .
|
||||||
|
tar zcf sdk-windows-32bit-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll
|
||||||
|
rm -rf README.md proxy-sdk.h proxy-sdk.dll
|
||||||
|
|
||||||
|
sudo rm /usr/local/go
|
||||||
|
sudo ln -s /usr/local/go1.8.5 /usr/local/go
|
||||||
|
|
||||||
|
echo "done."
|
||||||
25
sdk/windows-linux/sdk.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"C"
|
||||||
|
|
||||||
|
sdk "github.com/snail007/goproxy/sdk/android-ios"
|
||||||
|
)
|
||||||
|
|
||||||
|
//export Start
|
||||||
|
func Start(serviceID *C.char, serviceArgsStr *C.char) (errStr *C.char) {
|
||||||
|
return C.CString(sdk.Start(C.GoString(serviceID), C.GoString(serviceArgsStr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export Stop
|
||||||
|
func Stop(serviceID *C.char) {
|
||||||
|
sdk.Stop(C.GoString(serviceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export Version
|
||||||
|
func Version() (ver *C.char) {
|
||||||
|
return C.CString(sdk.Version())
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
123
services/args.go
@ -1,6 +1,10 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import "golang.org/x/crypto/ssh"
|
import (
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
// tcp := app.Command("tcp", "proxy on tcp mode")
|
// tcp := app.Command("tcp", "proxy on tcp mode")
|
||||||
// t := tcp.Flag("tcp-timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int()
|
// t := tcp.Flag("tcp-timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int()
|
||||||
@ -16,8 +20,52 @@ const (
|
|||||||
CONN_SERVER_HEARBEAT = uint8(3)
|
CONN_SERVER_HEARBEAT = uint8(3)
|
||||||
CONN_SERVER = uint8(4)
|
CONN_SERVER = uint8(4)
|
||||||
CONN_CLIENT = uint8(5)
|
CONN_CLIENT = uint8(5)
|
||||||
|
CONN_SERVER_MUX = uint8(6)
|
||||||
|
CONN_CLIENT_MUX = uint8(7)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MuxServerArgs struct {
|
||||||
|
Parent *string
|
||||||
|
ParentType *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
IsUDP *bool
|
||||||
|
Key *string
|
||||||
|
Remote *string
|
||||||
|
Timeout *int
|
||||||
|
Route *[]string
|
||||||
|
Mgr *MuxServerManager
|
||||||
|
IsCompress *bool
|
||||||
|
SessionCount *int
|
||||||
|
KCP kcpcfg.KCPConfigArgs
|
||||||
|
}
|
||||||
|
type MuxClientArgs struct {
|
||||||
|
Parent *string
|
||||||
|
ParentType *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Key *string
|
||||||
|
Timeout *int
|
||||||
|
IsCompress *bool
|
||||||
|
SessionCount *int
|
||||||
|
KCP kcpcfg.KCPConfigArgs
|
||||||
|
}
|
||||||
|
type MuxBridgeArgs struct {
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
LocalType *string
|
||||||
|
Timeout *int
|
||||||
|
IsCompress *bool
|
||||||
|
KCP kcpcfg.KCPConfigArgs
|
||||||
|
}
|
||||||
type TunnelServerArgs struct {
|
type TunnelServerArgs struct {
|
||||||
Parent *string
|
Parent *string
|
||||||
CertFile *string
|
CertFile *string
|
||||||
@ -31,6 +79,7 @@ type TunnelServerArgs struct {
|
|||||||
Timeout *int
|
Timeout *int
|
||||||
Route *[]string
|
Route *[]string
|
||||||
Mgr *TunnelServerManager
|
Mgr *TunnelServerManager
|
||||||
|
Mux *bool
|
||||||
}
|
}
|
||||||
type TunnelClientArgs struct {
|
type TunnelClientArgs struct {
|
||||||
Parent *string
|
Parent *string
|
||||||
@ -40,6 +89,7 @@ type TunnelClientArgs struct {
|
|||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
Key *string
|
Key *string
|
||||||
Timeout *int
|
Timeout *int
|
||||||
|
Mux *bool
|
||||||
}
|
}
|
||||||
type TunnelBridgeArgs struct {
|
type TunnelBridgeArgs struct {
|
||||||
Parent *string
|
Parent *string
|
||||||
@ -49,6 +99,7 @@ type TunnelBridgeArgs struct {
|
|||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
Local *string
|
Local *string
|
||||||
Timeout *int
|
Timeout *int
|
||||||
|
Mux *bool
|
||||||
}
|
}
|
||||||
type TCPArgs struct {
|
type TCPArgs struct {
|
||||||
Parent *string
|
Parent *string
|
||||||
@ -60,16 +111,16 @@ type TCPArgs struct {
|
|||||||
ParentType *string
|
ParentType *string
|
||||||
LocalType *string
|
LocalType *string
|
||||||
Timeout *int
|
Timeout *int
|
||||||
PoolSize *int
|
|
||||||
CheckParentInterval *int
|
CheckParentInterval *int
|
||||||
KCPMethod *string
|
KCP kcpcfg.KCPConfigArgs
|
||||||
KCPKey *string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPArgs struct {
|
type HTTPArgs struct {
|
||||||
Parent *string
|
Parent *string
|
||||||
CertFile *string
|
CertFile *string
|
||||||
KeyFile *string
|
KeyFile *string
|
||||||
|
CaCertFile *string
|
||||||
|
CaCertBytes []byte
|
||||||
CertBytes []byte
|
CertBytes []byte
|
||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
Local *string
|
Local *string
|
||||||
@ -87,7 +138,6 @@ type HTTPArgs struct {
|
|||||||
ParentType *string
|
ParentType *string
|
||||||
LocalType *string
|
LocalType *string
|
||||||
Timeout *int
|
Timeout *int
|
||||||
PoolSize *int
|
|
||||||
CheckParentInterval *int
|
CheckParentInterval *int
|
||||||
SSHKeyFile *string
|
SSHKeyFile *string
|
||||||
SSHKeyFileSalt *string
|
SSHKeyFileSalt *string
|
||||||
@ -95,9 +145,14 @@ type HTTPArgs struct {
|
|||||||
SSHUser *string
|
SSHUser *string
|
||||||
SSHKeyBytes []byte
|
SSHKeyBytes []byte
|
||||||
SSHAuthMethod ssh.AuthMethod
|
SSHAuthMethod ssh.AuthMethod
|
||||||
KCPMethod *string
|
KCP kcpcfg.KCPConfigArgs
|
||||||
KCPKey *string
|
|
||||||
LocalIPS *[]string
|
LocalIPS *[]string
|
||||||
|
DNSAddress *string
|
||||||
|
DNSTTL *int
|
||||||
|
LocalKey *string
|
||||||
|
ParentKey *string
|
||||||
|
LocalCompress *bool
|
||||||
|
ParentCompress *bool
|
||||||
}
|
}
|
||||||
type UDPArgs struct {
|
type UDPArgs struct {
|
||||||
Parent *string
|
Parent *string
|
||||||
@ -108,7 +163,6 @@ type UDPArgs struct {
|
|||||||
Local *string
|
Local *string
|
||||||
ParentType *string
|
ParentType *string
|
||||||
Timeout *int
|
Timeout *int
|
||||||
PoolSize *int
|
|
||||||
CheckParentInterval *int
|
CheckParentInterval *int
|
||||||
}
|
}
|
||||||
type SocksArgs struct {
|
type SocksArgs struct {
|
||||||
@ -118,6 +172,8 @@ type SocksArgs struct {
|
|||||||
LocalType *string
|
LocalType *string
|
||||||
CertFile *string
|
CertFile *string
|
||||||
KeyFile *string
|
KeyFile *string
|
||||||
|
CaCertFile *string
|
||||||
|
CaCertBytes []byte
|
||||||
CertBytes []byte
|
CertBytes []byte
|
||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
SSHKeyFile *string
|
SSHKeyFile *string
|
||||||
@ -137,13 +193,60 @@ type SocksArgs struct {
|
|||||||
AuthURLOkCode *int
|
AuthURLOkCode *int
|
||||||
AuthURLTimeout *int
|
AuthURLTimeout *int
|
||||||
AuthURLRetry *int
|
AuthURLRetry *int
|
||||||
KCPMethod *string
|
KCP kcpcfg.KCPConfigArgs
|
||||||
KCPKey *string
|
|
||||||
UDPParent *string
|
UDPParent *string
|
||||||
UDPLocal *string
|
UDPLocal *string
|
||||||
LocalIPS *[]string
|
LocalIPS *[]string
|
||||||
|
DNSAddress *string
|
||||||
|
DNSTTL *int
|
||||||
|
LocalKey *string
|
||||||
|
ParentKey *string
|
||||||
|
LocalCompress *bool
|
||||||
|
ParentCompress *bool
|
||||||
|
}
|
||||||
|
type SPSArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CaCertFile *string
|
||||||
|
CaCertBytes []byte
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
ParentType *string
|
||||||
|
LocalType *string
|
||||||
|
Timeout *int
|
||||||
|
KCP kcpcfg.KCPConfigArgs
|
||||||
|
ParentServiceType *string
|
||||||
|
DNSAddress *string
|
||||||
|
DNSTTL *int
|
||||||
|
AuthFile *string
|
||||||
|
Auth *[]string
|
||||||
|
AuthURL *string
|
||||||
|
AuthURLOkCode *int
|
||||||
|
AuthURLTimeout *int
|
||||||
|
AuthURLRetry *int
|
||||||
|
LocalIPS *[]string
|
||||||
|
ParentAuth *string
|
||||||
|
LocalKey *string
|
||||||
|
ParentKey *string
|
||||||
|
LocalCompress *bool
|
||||||
|
ParentCompress *bool
|
||||||
|
DisableHTTP *bool
|
||||||
|
DisableSocks5 *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SPSArgs) Protocol() string {
|
||||||
|
switch *a.LocalType {
|
||||||
|
case TYPE_TLS:
|
||||||
|
return TYPE_TLS
|
||||||
|
case TYPE_TCP:
|
||||||
|
return TYPE_TCP
|
||||||
|
case TYPE_KCP:
|
||||||
|
return TYPE_KCP
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
func (a *TCPArgs) Protocol() string {
|
func (a *TCPArgs) Protocol() string {
|
||||||
switch *a.LocalType {
|
switch *a.LocalType {
|
||||||
case TYPE_TLS:
|
case TYPE_TLS:
|
||||||
|
|||||||
303
services/http.go
@ -4,48 +4,71 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
"github.com/snail007/goproxy/utils/conncrypt"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTP struct {
|
type HTTP struct {
|
||||||
outPool utils.OutPool
|
outPool utils.OutConn
|
||||||
cfg HTTPArgs
|
cfg HTTPArgs
|
||||||
checker utils.Checker
|
checker utils.Checker
|
||||||
basicAuth utils.BasicAuth
|
basicAuth utils.BasicAuth
|
||||||
sshClient *ssh.Client
|
sshClient *ssh.Client
|
||||||
lockChn chan bool
|
lockChn chan bool
|
||||||
|
domainResolver utils.DomainResolver
|
||||||
|
isStop bool
|
||||||
|
serverChannels []*utils.ServerChannel
|
||||||
|
userConns utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP() Service {
|
func NewHTTP() Service {
|
||||||
return &HTTP{
|
return &HTTP{
|
||||||
outPool: utils.OutPool{},
|
outPool: utils.OutConn{},
|
||||||
cfg: HTTPArgs{},
|
cfg: HTTPArgs{},
|
||||||
checker: utils.Checker{},
|
checker: utils.Checker{},
|
||||||
basicAuth: utils.BasicAuth{},
|
basicAuth: utils.BasicAuth{},
|
||||||
lockChn: make(chan bool, 1),
|
lockChn: make(chan bool, 1),
|
||||||
|
isStop: false,
|
||||||
|
serverChannels: []*utils.ServerChannel{},
|
||||||
|
userConns: utils.NewConcurrentMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *HTTP) CheckArgs() {
|
func (s *HTTP) CheckArgs() (err error) {
|
||||||
var err error
|
|
||||||
if *s.cfg.Parent != "" && *s.cfg.ParentType == "" {
|
if *s.cfg.Parent != "" && *s.cfg.ParentType == "" {
|
||||||
log.Fatalf("parent type unkown,use -T <tls|tcp|ssh>")
|
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|ssh|kcp>")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" {
|
if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" {
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.CaCertFile != "" {
|
||||||
|
s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read ca file error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "ssh" {
|
if *s.cfg.ParentType == "ssh" {
|
||||||
if *s.cfg.SSHUser == "" {
|
if *s.cfg.SSHUser == "" {
|
||||||
log.Fatalf("ssh user required")
|
err = fmt.Errorf("ssh user required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||||
log.Fatalf("ssh password or key required")
|
err = fmt.Errorf("ssh password or key required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if *s.cfg.SSHPassword != "" {
|
if *s.cfg.SSHPassword != "" {
|
||||||
@ -54,7 +77,8 @@ func (s *HTTP) CheckArgs() {
|
|||||||
var SSHSigner ssh.Signer
|
var SSHSigner ssh.Signer
|
||||||
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("read key file ERR: %s", err)
|
err = fmt.Errorf("read key file ERR: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.SSHKeyFileSalt != "" {
|
if *s.cfg.SSHKeyFileSalt != "" {
|
||||||
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||||
@ -62,28 +86,39 @@ func (s *HTTP) CheckArgs() {
|
|||||||
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("parse ssh private key fail,ERR: %s", err)
|
err = fmt.Errorf("parse ssh private key fail,ERR: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *HTTP) InitService() {
|
func (s *HTTP) InitService() (err error) {
|
||||||
s.InitBasicAuth()
|
s.InitBasicAuth()
|
||||||
if *s.cfg.Parent != "" {
|
if *s.cfg.Parent != "" {
|
||||||
s.checker = utils.NewChecker(*s.cfg.HTTPTimeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct)
|
s.checker = utils.NewChecker(*s.cfg.HTTPTimeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct, s.log)
|
||||||
|
}
|
||||||
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
(*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "ssh" {
|
if *s.cfg.ParentType == "ssh" {
|
||||||
err := s.ConnectSSH()
|
err = s.ConnectSSH()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("init service fail, ERR: %s", err)
|
err = fmt.Errorf("init service fail, ERR: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
//循环检查ssh网络连通性
|
//循环检查ssh网络连通性
|
||||||
for {
|
for {
|
||||||
conn, err := utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout*2)
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = conn.Write([]byte{0})
|
_, err = conn.Write([]byte{0})
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.sshClient != nil {
|
if s.sshClient != nil {
|
||||||
@ -92,7 +127,7 @@ func (s *HTTP) InitService() {
|
|||||||
s.sshClient.Conn.Close()
|
s.sshClient.Conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("ssh offline, retrying...")
|
s.log.Printf("ssh offline, retrying...")
|
||||||
s.ConnectSSH()
|
s.ConnectSSH()
|
||||||
} else {
|
} else {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -101,34 +136,66 @@ func (s *HTTP) InitService() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *HTTP) StopService() {
|
func (s *HTTP) StopService() {
|
||||||
if s.outPool.Pool != nil {
|
defer func() {
|
||||||
s.outPool.Pool.ReleaseAll()
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop http(s) service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service http(s) stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
s.checker.Stop()
|
||||||
|
if s.sshClient != nil {
|
||||||
|
s.sshClient.Close()
|
||||||
|
}
|
||||||
|
for _, sc := range s.serverChannels {
|
||||||
|
if sc.Listener != nil && *sc.Listener != nil {
|
||||||
|
(*sc.Listener).Close()
|
||||||
|
}
|
||||||
|
if sc.UDPListener != nil {
|
||||||
|
(*sc.UDPListener).Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *HTTP) Start(args interface{}) (err error) {
|
func (s *HTTP) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(HTTPArgs)
|
s.cfg = args.(HTTPArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
if *s.cfg.Parent != "" {
|
|
||||||
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
|
||||||
s.InitOutConnPool()
|
|
||||||
}
|
|
||||||
s.InitService()
|
|
||||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
sc := utils.NewServerChannel(host, p)
|
|
||||||
if *s.cfg.LocalType == TYPE_TCP {
|
|
||||||
err = sc.ListenTCP(s.callback)
|
|
||||||
} else if *s.cfg.LocalType == TYPE_TLS {
|
|
||||||
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback)
|
|
||||||
} else if *s.cfg.LocalType == TYPE_KCP {
|
|
||||||
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
s.InitOutConnPool()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range strings.Split(*s.cfg.Local, ",") {
|
||||||
|
if addr != "" {
|
||||||
|
host, port, _ := net.SplitHostPort(addr)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p, s.log)
|
||||||
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
|
err = sc.ListenTCP(s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
|
err = sc.ListenKCP(s.cfg.KCP, s.callback, s.log)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||||
|
s.serverChannels = append(s.serverChannels, &sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,45 +205,53 @@ func (s *HTTP) Clean() {
|
|||||||
func (s *HTTP) callback(inConn net.Conn) {
|
func (s *HTTP) callback(inConn net.Conn) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("http(s) conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
s.log.Printf("http(s) conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if *s.cfg.LocalCompress {
|
||||||
|
inConn = utils.NewCompConn(inConn)
|
||||||
|
}
|
||||||
|
if *s.cfg.LocalKey != "" {
|
||||||
|
inConn = conncrypt.New(inConn, &conncrypt.Config{
|
||||||
|
Password: *s.cfg.LocalKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
var err interface{}
|
var err interface{}
|
||||||
var req utils.HTTPRequest
|
var req utils.HTTPRequest
|
||||||
req, err = utils.NewHTTPRequest(&inConn, 4096, s.IsBasicAuth(), &s.basicAuth)
|
req, err = utils.NewHTTPRequest(&inConn, 4096, s.IsBasicAuth(), &s.basicAuth, s.log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
log.Printf("decoder error , from %s, ERR:%s", inConn.RemoteAddr(), err)
|
s.log.Printf("decoder error , from %s, ERR:%s", inConn.RemoteAddr(), err)
|
||||||
}
|
}
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
address := req.Host
|
address := req.Host
|
||||||
|
host, _, _ := net.SplitHostPort(address)
|
||||||
useProxy := true
|
useProxy := false
|
||||||
if *s.cfg.Parent == "" {
|
if !utils.IsIternalIP(host, *s.cfg.Always) {
|
||||||
useProxy = false
|
|
||||||
} else if *s.cfg.Always {
|
|
||||||
useProxy = true
|
useProxy = true
|
||||||
} else {
|
if *s.cfg.Parent == "" {
|
||||||
if req.IsHTTPS() {
|
useProxy = false
|
||||||
s.checker.Add(address, true, req.Method, "", nil)
|
} else if *s.cfg.Always {
|
||||||
|
useProxy = true
|
||||||
} else {
|
} else {
|
||||||
s.checker.Add(address, false, req.Method, req.URL, req.HeadBuf)
|
k := s.Resolve(address)
|
||||||
|
s.checker.Add(k)
|
||||||
|
//var n, m uint
|
||||||
|
useProxy, _, _ = s.checker.IsBlocked(k)
|
||||||
|
//s.log.Printf("blocked ? : %v, %s , fail:%d ,success:%d", useProxy, address, n, m)
|
||||||
}
|
}
|
||||||
//var n, m uint
|
|
||||||
useProxy, _, _ = s.checker.IsBlocked(req.Host)
|
|
||||||
//log.Printf("blocked ? : %v, %s , fail:%d ,success:%d", useProxy, address, n, m)
|
|
||||||
}
|
}
|
||||||
log.Printf("use proxy : %v, %s", useProxy, address)
|
|
||||||
|
s.log.Printf("use proxy : %v, %s", useProxy, address)
|
||||||
|
|
||||||
err = s.OutToTCP(useProxy, address, &inConn, &req)
|
err = s.OutToTCP(useProxy, address, &inConn, &req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if *s.cfg.Parent == "" {
|
if *s.cfg.Parent == "" {
|
||||||
log.Printf("connect to %s fail, ERR:%s", address, err)
|
s.log.Printf("connect to %s fail, ERR:%s", address, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("connect to %s parent %s fail", *s.cfg.ParentType, *s.cfg.Parent)
|
s.log.Printf("connect to %s parent %s fail", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
}
|
}
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
}
|
}
|
||||||
@ -191,58 +266,75 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
var _outConn interface{}
|
|
||||||
tryCount := 0
|
tryCount := 0
|
||||||
maxTryCount := 5
|
maxTryCount := 5
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
if useProxy {
|
if useProxy {
|
||||||
if *s.cfg.ParentType == "ssh" {
|
if *s.cfg.ParentType == "ssh" {
|
||||||
outConn, err = s.getSSHConn(address)
|
outConn, err = s.getSSHConn(address)
|
||||||
} else {
|
} else {
|
||||||
//log.Printf("%v", s.outPool)
|
// s.log.Printf("%v", s.outPool)
|
||||||
_outConn, err = s.outPool.Pool.Get()
|
outConn, err = s.outPool.Get()
|
||||||
if err == nil {
|
|
||||||
outConn = _outConn.(net.Conn)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outConn, err = utils.ConnectHost(address, *s.cfg.Timeout)
|
outConn, err = utils.ConnectHost(s.Resolve(address), *s.cfg.Timeout)
|
||||||
}
|
}
|
||||||
tryCount++
|
tryCount++
|
||||||
if err == nil || tryCount > maxTryCount {
|
if err == nil || tryCount > maxTryCount {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
log.Printf("connect to %s , err:%s,retrying...", *s.cfg.Parent, err)
|
s.log.Printf("connect to %s , err:%s,retrying...", *s.cfg.Parent, err)
|
||||||
time.Sleep(time.Second * 2)
|
time.Sleep(time.Second * 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
s.log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if *s.cfg.ParentCompress {
|
||||||
|
outConn = utils.NewCompConn(outConn)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentKey != "" {
|
||||||
|
outConn = conncrypt.New(outConn, &conncrypt.Config{
|
||||||
|
Password: *s.cfg.ParentKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
outAddr := outConn.RemoteAddr().String()
|
outAddr := outConn.RemoteAddr().String()
|
||||||
//outLocalAddr := outConn.LocalAddr().String()
|
//outLocalAddr := outConn.LocalAddr().String()
|
||||||
|
|
||||||
if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") {
|
if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") {
|
||||||
//https无上级或者上级非代理,proxy需要响应connect请求,并直连目标
|
//https无上级或者上级非代理,proxy需要响应connect请求,并直连目标
|
||||||
err = req.HTTPSReply()
|
err = req.HTTPSReply()
|
||||||
} else {
|
} else {
|
||||||
//https或者http,上级是代理,proxy需要转发
|
//https或者http,上级是代理,proxy需要转发
|
||||||
_, err = outConn.Write(req.HeadBuf)
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
//直连目标或上级非代理,清理HTTP头部的代理头信息
|
||||||
|
if !useProxy || *s.cfg.ParentType == "ssh" {
|
||||||
|
_, err = outConn.Write(utils.RemoveProxyHeaders(req.HeadBuf))
|
||||||
|
} else {
|
||||||
|
_, err = outConn.Write(req.HeadBuf)
|
||||||
|
}
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("write to %s , err:%s", *s.cfg.Parent, err)
|
s.log.Printf("write to %s , err:%s", *s.cfg.Parent, err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
||||||
log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host)
|
s.log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host)
|
||||||
})
|
s.userConns.Remove(inAddr)
|
||||||
log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, req.Host)
|
}, s.log)
|
||||||
|
s.log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, req.Host)
|
||||||
|
if c, ok := s.userConns.Get(inAddr); ok {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.userConns.Set(inAddr, inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +342,7 @@ func (s *HTTP) getSSHConn(host string) (outConn net.Conn, err interface{}) {
|
|||||||
maxTryCount := 1
|
maxTryCount := 1
|
||||||
tryCount := 0
|
tryCount := 0
|
||||||
RETRY:
|
RETRY:
|
||||||
if tryCount >= maxTryCount {
|
if tryCount >= maxTryCount || s.isStop {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wait := make(chan bool, 1)
|
wait := make(chan bool, 1)
|
||||||
@ -269,7 +361,7 @@ RETRY:
|
|||||||
err = fmt.Errorf("ssh dial %s timeout", host)
|
err = fmt.Errorf("ssh dial %s timeout", host)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
s.log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||||
e := s.ConnectSSH()
|
e := s.ConnectSSH()
|
||||||
if e == nil {
|
if e == nil {
|
||||||
tryCount++
|
tryCount++
|
||||||
@ -299,7 +391,7 @@ func (s *HTTP) ConnectSSH() (err error) {
|
|||||||
if s.sshClient != nil {
|
if s.sshClient != nil {
|
||||||
s.sshClient.Close()
|
s.sshClient.Close()
|
||||||
}
|
}
|
||||||
s.sshClient, err = ssh.Dial("tcp", *s.cfg.Parent, &config)
|
s.sshClient, err = ssh.Dial("tcp", s.Resolve(*s.cfg.Parent), &config)
|
||||||
<-s.lockChn
|
<-s.lockChn
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -307,24 +399,25 @@ func (s *HTTP) InitOutConnPool() {
|
|||||||
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP {
|
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP {
|
||||||
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
||||||
//parent string, timeout int, InitialCap int, MaxCap int
|
//parent string, timeout int, InitialCap int, MaxCap int
|
||||||
s.outPool = utils.NewOutPool(
|
s.outPool = utils.NewOutConn(
|
||||||
*s.cfg.CheckParentInterval,
|
*s.cfg.CheckParentInterval,
|
||||||
*s.cfg.ParentType,
|
*s.cfg.ParentType,
|
||||||
*s.cfg.KCPMethod,
|
s.cfg.KCP,
|
||||||
*s.cfg.KCPKey,
|
s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes,
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes,
|
s.Resolve(*s.cfg.Parent),
|
||||||
*s.cfg.Parent,
|
|
||||||
*s.cfg.Timeout,
|
*s.cfg.Timeout,
|
||||||
*s.cfg.PoolSize,
|
|
||||||
*s.cfg.PoolSize*2,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *HTTP) InitBasicAuth() (err error) {
|
func (s *HTTP) InitBasicAuth() (err error) {
|
||||||
s.basicAuth = utils.NewBasicAuth()
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
s.basicAuth = utils.NewBasicAuth(&(*s).domainResolver, s.log)
|
||||||
|
} else {
|
||||||
|
s.basicAuth = utils.NewBasicAuth(nil, s.log)
|
||||||
|
}
|
||||||
if *s.cfg.AuthURL != "" {
|
if *s.cfg.AuthURL != "" {
|
||||||
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||||
log.Printf("auth from %s", *s.cfg.AuthURL)
|
s.log.Printf("auth from %s", *s.cfg.AuthURL)
|
||||||
}
|
}
|
||||||
if *s.cfg.AuthFile != "" {
|
if *s.cfg.AuthFile != "" {
|
||||||
var n = 0
|
var n = 0
|
||||||
@ -333,11 +426,11 @@ func (s *HTTP) InitBasicAuth() (err error) {
|
|||||||
err = fmt.Errorf("auth-file ERR:%s", err)
|
err = fmt.Errorf("auth-file ERR:%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
s.log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
||||||
}
|
}
|
||||||
if len(*s.cfg.Auth) > 0 {
|
if len(*s.cfg.Auth) > 0 {
|
||||||
n := s.basicAuth.Add(*s.cfg.Auth)
|
n := s.basicAuth.Add(*s.cfg.Auth)
|
||||||
log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
s.log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -355,7 +448,11 @@ func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
|
|||||||
}
|
}
|
||||||
if inPort == outPort {
|
if inPort == outPort {
|
||||||
var outIPs []net.IP
|
var outIPs []net.IP
|
||||||
outIPs, err = net.LookupIP(outDomain)
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
outIPs = []net.IP{net.ParseIP(s.Resolve(outDomain))}
|
||||||
|
} else {
|
||||||
|
outIPs, err = net.LookupIP(outDomain)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, ip := range outIPs {
|
for _, ip := range outIPs {
|
||||||
if ip.String() == inIP {
|
if ip.String() == inIP {
|
||||||
@ -379,3 +476,13 @@ func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
func (s *HTTP) Resolve(address string) string {
|
||||||
|
if *s.cfg.DNSAddress == "" {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
ip, err := s.domainResolver.Resolve(address)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("dns error %s , ERR:%s", address, err)
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|||||||
24
services/kcpcfg/args.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package kcpcfg
|
||||||
|
|
||||||
|
import kcp "github.com/xtaci/kcp-go"
|
||||||
|
|
||||||
|
type KCPConfigArgs struct {
|
||||||
|
Key *string
|
||||||
|
Crypt *string
|
||||||
|
Mode *string
|
||||||
|
MTU *int
|
||||||
|
SndWnd *int
|
||||||
|
RcvWnd *int
|
||||||
|
DataShard *int
|
||||||
|
ParityShard *int
|
||||||
|
DSCP *int
|
||||||
|
NoComp *bool
|
||||||
|
AckNodelay *bool
|
||||||
|
NoDelay *int
|
||||||
|
Interval *int
|
||||||
|
Resend *int
|
||||||
|
NoCongestion *int
|
||||||
|
SockBuf *int
|
||||||
|
KeepAlive *int
|
||||||
|
Block kcp.BlockCrypt
|
||||||
|
}
|
||||||
284
services/mux_bridge.go
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
logger "log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
//"github.com/xtaci/smux"
|
||||||
|
smux "github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxBridge struct {
|
||||||
|
cfg MuxBridgeArgs
|
||||||
|
clientControlConns utils.ConcurrentMap
|
||||||
|
serverConns utils.ConcurrentMap
|
||||||
|
router utils.ClientKeyRouter
|
||||||
|
l *sync.Mutex
|
||||||
|
isStop bool
|
||||||
|
sc *utils.ServerChannel
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxBridge() Service {
|
||||||
|
b := &MuxBridge{
|
||||||
|
cfg: MuxBridgeArgs{},
|
||||||
|
clientControlConns: utils.NewConcurrentMap(),
|
||||||
|
serverConns: utils.NewConcurrentMap(),
|
||||||
|
l: &sync.Mutex{},
|
||||||
|
isStop: false,
|
||||||
|
}
|
||||||
|
b.router = utils.NewClientKeyRouter(&b.clientControlConns, 50000)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxBridge) InitService() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) CheckArgs() (err error) {
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
err = fmt.Errorf("cert and key file required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) StopService() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop bridge service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service bridge stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
if s.sc != nil && (*s.sc).Listener != nil {
|
||||||
|
(*(*s.sc).Listener).Close()
|
||||||
|
}
|
||||||
|
for _, g := range s.clientControlConns.Items() {
|
||||||
|
for _, session := range g.(utils.ConcurrentMap).Items() {
|
||||||
|
(session.(*smux.Session)).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range s.serverConns.Items() {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
|
s.cfg = args.(MuxBridgeArgs)
|
||||||
|
if err = s.CheckArgs(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p, s.log)
|
||||||
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
|
err = sc.ListenTCP(s.handler)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.handler)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
|
err = sc.ListenKCP(s.cfg.KCP, s.handler, s.log)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.sc = &sc
|
||||||
|
s.log.Printf("%s bridge on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) handler(inConn net.Conn) {
|
||||||
|
reader := bufio.NewReader(inConn)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var connType uint8
|
||||||
|
var key string
|
||||||
|
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
err = utils.ReadPacket(reader, &connType, &key)
|
||||||
|
inConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch connType {
|
||||||
|
case CONN_SERVER:
|
||||||
|
var serverID string
|
||||||
|
inAddr := inConn.RemoteAddr().String()
|
||||||
|
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
err = utils.ReadPacketData(reader, &serverID)
|
||||||
|
inConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("server connection %s %s connected", serverID, key)
|
||||||
|
if c, ok := s.serverConns.Get(inAddr); ok {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.serverConns.Set(inAddr, &inConn)
|
||||||
|
session, err := smux.Server(inConn, nil)
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
s.log.Printf("server session error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream, err := session.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
s.serverConns.Remove(inAddr)
|
||||||
|
s.log.Printf("server connection %s %s released", serverID, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
s.log.Printf("bridge callback crashed,err: %s", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.callback(stream, serverID, key)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
case CONN_CLIENT:
|
||||||
|
s.log.Printf("client connection %s connected", key)
|
||||||
|
session, err := smux.Client(inConn, nil)
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
s.log.Printf("client session error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyInfo := strings.Split(key, "-")
|
||||||
|
if len(keyInfo) != 2 {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
s.log.Printf("client key format error,key:%s", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupKey := keyInfo[0]
|
||||||
|
index := keyInfo[1]
|
||||||
|
s.l.Lock()
|
||||||
|
defer s.l.Unlock()
|
||||||
|
if !s.clientControlConns.Has(groupKey) {
|
||||||
|
item := utils.NewConcurrentMap()
|
||||||
|
s.clientControlConns.Set(groupKey, &item)
|
||||||
|
}
|
||||||
|
_group, _ := s.clientControlConns.Get(groupKey)
|
||||||
|
group := _group.(*utils.ConcurrentMap)
|
||||||
|
if v, ok := group.Get(index); ok {
|
||||||
|
v.(*smux.Session).Close()
|
||||||
|
}
|
||||||
|
group.Set(index, session)
|
||||||
|
// s.clientControlConns.Set(key, session)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session.IsClosed() {
|
||||||
|
s.l.Lock()
|
||||||
|
defer s.l.Unlock()
|
||||||
|
if sess, ok := group.Get(index); ok && sess.(*smux.Session).IsClosed() {
|
||||||
|
group.Remove(index)
|
||||||
|
s.log.Printf("client connection %s released", key)
|
||||||
|
}
|
||||||
|
if group.IsEmpty() {
|
||||||
|
s.clientControlConns.Remove(groupKey)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
//s.log.Printf("set client session,key: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) {
|
||||||
|
try := 20
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try--
|
||||||
|
if try == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if key == "*" {
|
||||||
|
key = s.router.GetKey()
|
||||||
|
}
|
||||||
|
_group, ok := s.clientControlConns.Get(key)
|
||||||
|
if !ok {
|
||||||
|
s.log.Printf("client %s session not exists for server stream %s, retrying...", key, serverID)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
group := _group.(*utils.ConcurrentMap)
|
||||||
|
keys := group.Keys()
|
||||||
|
keysLen := len(keys)
|
||||||
|
i := 0
|
||||||
|
if keysLen > 0 {
|
||||||
|
i = rand.Intn(keysLen)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("client %s session empty for server stream %s, retrying...", key, serverID)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := keys[i]
|
||||||
|
s.log.Printf("select client : %s-%s", key, index)
|
||||||
|
session, _ := group.Get(index)
|
||||||
|
//session.(*smux.Session).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
stream, err := session.(*smux.Session).OpenStream()
|
||||||
|
//session.(*smux.Session).SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("%s client session open stream %s fail, err: %s, retrying...", key, serverID, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
s.log.Printf("stream %s -> %s created", serverID, key)
|
||||||
|
die1 := make(chan bool, 1)
|
||||||
|
die2 := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
io.Copy(stream, inConn)
|
||||||
|
die1 <- true
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
io.Copy(inConn, stream)
|
||||||
|
die2 <- true
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-die1:
|
||||||
|
case <-die2:
|
||||||
|
}
|
||||||
|
stream.Close()
|
||||||
|
inConn.Close()
|
||||||
|
s.log.Printf("%s server %s stream released", key, serverID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
306
services/mux_client.go
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
logger "log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
"github.com/golang/snappy"
|
||||||
|
//"github.com/xtaci/smux"
|
||||||
|
smux "github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxClient struct {
|
||||||
|
cfg MuxClientArgs
|
||||||
|
isStop bool
|
||||||
|
sessions utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxClient() Service {
|
||||||
|
return &MuxClient{
|
||||||
|
cfg: MuxClientArgs{},
|
||||||
|
isStop: false,
|
||||||
|
sessions: utils.NewConcurrentMap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxClient) InitService() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxClient) CheckArgs() (err error) {
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
s.log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("parent required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
err = fmt.Errorf("cert and key file required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "tls" {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxClient) StopService() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop client service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service client stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
for _, sess := range s.sessions.Items() {
|
||||||
|
sess.(*smux.Session).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *MuxClient) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
|
s.cfg = args.(MuxClientArgs)
|
||||||
|
if err = s.CheckArgs(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("client started")
|
||||||
|
count := 1
|
||||||
|
if *s.cfg.SessionCount > 0 {
|
||||||
|
count = *s.cfg.SessionCount
|
||||||
|
}
|
||||||
|
for i := 1; i <= count; i++ {
|
||||||
|
key := fmt.Sprintf("worker[%d]", i)
|
||||||
|
s.log.Printf("session %s started", key)
|
||||||
|
go func(i int) {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("session worker crashed: %s", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, err := s.getParentConn()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("connection err: %s, retrying...", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = conn.Write(utils.BuildPacket(CONN_CLIENT, fmt.Sprintf("%s-%d", *s.cfg.Key, i)))
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
s.log.Printf("connection err: %s, retrying...", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
session, err := smux.Server(conn, nil)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("session err: %s, retrying...", err)
|
||||||
|
conn.Close()
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _sess, ok := s.sessions.Get(key); ok {
|
||||||
|
_sess.(*smux.Session).Close()
|
||||||
|
}
|
||||||
|
s.sessions.Set(key, session)
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream, err := session.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("accept stream err: %s, retrying...", err)
|
||||||
|
session.Close()
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stream handler crashed: %s", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var ID, clientLocalAddr, serverID string
|
||||||
|
stream.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID)
|
||||||
|
stream.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read stream signal err: %s", err)
|
||||||
|
stream.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("worker[%d] signal revecived,server %s stream %s %s", i, serverID, ID, clientLocalAddr)
|
||||||
|
protocol := clientLocalAddr[:3]
|
||||||
|
localAddr := clientLocalAddr[4:]
|
||||||
|
if protocol == "udp" {
|
||||||
|
s.ServeUDP(stream, localAddr, ID)
|
||||||
|
} else {
|
||||||
|
s.ServeConn(stream, localAddr, ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxClient) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *MuxClient) getParentConn() (conn net.Conn, err error) {
|
||||||
|
if *s.cfg.ParentType == "tls" {
|
||||||
|
var _conn tls.Conn
|
||||||
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||||
|
if err == nil {
|
||||||
|
conn = net.Conn(&_conn)
|
||||||
|
}
|
||||||
|
} else if *s.cfg.ParentType == "kcp" {
|
||||||
|
conn, err = utils.ConnectKCPHost(*s.cfg.Parent, s.cfg.KCP)
|
||||||
|
} else {
|
||||||
|
conn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) {
|
||||||
|
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
srcAddr, body, err := utils.ReadUDPPacket(inConn)
|
||||||
|
inConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("udp packet revecived fail, err: %s", err)
|
||||||
|
s.log.Printf("connection %s released", ID)
|
||||||
|
inConn.Close()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
//s.log.Printf("udp packet revecived:%s,%v", srcAddr, body)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
s.log.Printf("client processUDPPacket crashed,err: %s", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.processUDPPacket(inConn, srcAddr, localAddr, body)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
func (s *MuxClient) processUDPPacket(inConn *smux.Stream, srcAddr, localAddr string, body []byte) {
|
||||||
|
dstAddr, err := net.ResolveUDPAddr("udp", localAddr)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("can't resolve address: %s", err)
|
||||||
|
inConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||||
|
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = conn.Write(body)
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//s.log.Printf("send udp packet to %s success", dstAddr.String())
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
length, _, err := conn.ReadFromUDP(buf)
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBody := buf[0:length]
|
||||||
|
//s.log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
||||||
|
bs := utils.UDPPacket(srcAddr, respBody)
|
||||||
|
(*inConn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = (*inConn).Write(bs)
|
||||||
|
(*inConn).SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("send udp response fail ,ERR:%s", err)
|
||||||
|
inConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//s.log.Printf("send udp response success ,from:%s ,%d ,%v", dstAddr.String(), len(bs), bs)
|
||||||
|
}
|
||||||
|
func (s *MuxClient) ServeConn(inConn *smux.Stream, localAddr, ID string) {
|
||||||
|
var err error
|
||||||
|
var outConn net.Conn
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
outConn, err = utils.ConnectHost(localAddr, *s.cfg.Timeout)
|
||||||
|
if err == nil || i == 3 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if i == 3 {
|
||||||
|
s.log.Printf("connect to %s err: %s, retrying...", localAddr, err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
inConn.Close()
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
s.log.Printf("build connection error, err: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Printf("stream %s created", ID)
|
||||||
|
if *s.cfg.IsCompress {
|
||||||
|
die1 := make(chan bool, 1)
|
||||||
|
die2 := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
io.Copy(outConn, snappy.NewReader(inConn))
|
||||||
|
die1 <- true
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
io.Copy(snappy.NewWriter(inConn), outConn)
|
||||||
|
die2 <- true
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-die1:
|
||||||
|
case <-die2:
|
||||||
|
}
|
||||||
|
outConn.Close()
|
||||||
|
inConn.Close()
|
||||||
|
s.log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||||
|
} else {
|
||||||
|
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||||
|
s.log.Printf("stream %s released", ID)
|
||||||
|
}, s.log)
|
||||||
|
}
|
||||||
|
}
|
||||||
455
services/mux_server.go
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
logger "log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
"github.com/golang/snappy"
|
||||||
|
//"github.com/xtaci/smux"
|
||||||
|
smux "github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxServer struct {
|
||||||
|
cfg MuxServerArgs
|
||||||
|
udpChn chan MuxUDPItem
|
||||||
|
sc utils.ServerChannel
|
||||||
|
sessions utils.ConcurrentMap
|
||||||
|
lockChn chan bool
|
||||||
|
isStop bool
|
||||||
|
udpConn *net.Conn
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type MuxServerManager struct {
|
||||||
|
cfg MuxServerArgs
|
||||||
|
udpChn chan MuxUDPItem
|
||||||
|
serverID string
|
||||||
|
servers []*Service
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxServerManager() Service {
|
||||||
|
return &MuxServerManager{
|
||||||
|
cfg: MuxServerArgs{},
|
||||||
|
udpChn: make(chan MuxUDPItem, 50000),
|
||||||
|
serverID: utils.Uniqueid(),
|
||||||
|
servers: []*Service{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxServerManager) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
|
s.cfg = args.(MuxServerArgs)
|
||||||
|
if err = s.CheckArgs(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("parent required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Printf("server id: %s", s.serverID)
|
||||||
|
//s.log.Printf("route:%v", *s.cfg.Route)
|
||||||
|
for _, _info := range *s.cfg.Route {
|
||||||
|
if _info == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
IsUDP := *s.cfg.IsUDP
|
||||||
|
if strings.HasPrefix(_info, "udp://") {
|
||||||
|
IsUDP = true
|
||||||
|
}
|
||||||
|
info := strings.TrimPrefix(_info, "udp://")
|
||||||
|
info = strings.TrimPrefix(info, "tcp://")
|
||||||
|
_routeInfo := strings.Split(info, "@")
|
||||||
|
server := NewMuxServer()
|
||||||
|
|
||||||
|
local := _routeInfo[0]
|
||||||
|
remote := _routeInfo[1]
|
||||||
|
KEY := *s.cfg.Key
|
||||||
|
if strings.HasPrefix(remote, "[") {
|
||||||
|
KEY = remote[1:strings.LastIndex(remote, "]")]
|
||||||
|
remote = remote[strings.LastIndex(remote, "]")+1:]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(remote, ":") {
|
||||||
|
remote = fmt.Sprintf("127.0.0.1%s", remote)
|
||||||
|
}
|
||||||
|
err = server.Start(MuxServerArgs{
|
||||||
|
CertBytes: s.cfg.CertBytes,
|
||||||
|
KeyBytes: s.cfg.KeyBytes,
|
||||||
|
Parent: s.cfg.Parent,
|
||||||
|
CertFile: s.cfg.CertFile,
|
||||||
|
KeyFile: s.cfg.KeyFile,
|
||||||
|
Local: &local,
|
||||||
|
IsUDP: &IsUDP,
|
||||||
|
Remote: &remote,
|
||||||
|
Key: &KEY,
|
||||||
|
Timeout: s.cfg.Timeout,
|
||||||
|
Mgr: s,
|
||||||
|
IsCompress: s.cfg.IsCompress,
|
||||||
|
SessionCount: s.cfg.SessionCount,
|
||||||
|
KCP: s.cfg.KCP,
|
||||||
|
ParentType: s.cfg.ParentType,
|
||||||
|
}, log)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.servers = append(s.servers, &server)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) StopService() {
|
||||||
|
for _, server := range s.servers {
|
||||||
|
(*server).Clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) CheckArgs() (err error) {
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
err = fmt.Errorf("cert and key file required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "tls" {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) InitService() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxServer() Service {
|
||||||
|
return &MuxServer{
|
||||||
|
cfg: MuxServerArgs{},
|
||||||
|
udpChn: make(chan MuxUDPItem, 50000),
|
||||||
|
lockChn: make(chan bool, 1),
|
||||||
|
sessions: utils.NewConcurrentMap(),
|
||||||
|
isStop: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MuxUDPItem struct {
|
||||||
|
packet *[]byte
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
srcAddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxServer) StopService() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop server service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service server stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
for _, sess := range s.sessions.Items() {
|
||||||
|
sess.(*smux.Session).Close()
|
||||||
|
}
|
||||||
|
if s.sc.Listener != nil {
|
||||||
|
(*s.sc.Listener).Close()
|
||||||
|
}
|
||||||
|
if s.sc.UDPListener != nil {
|
||||||
|
(*s.sc.UDPListener).Close()
|
||||||
|
}
|
||||||
|
if s.udpConn != nil {
|
||||||
|
(*s.udpConn).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *MuxServer) InitService() (err error) {
|
||||||
|
s.UDPConnDeamon()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) CheckArgs() (err error) {
|
||||||
|
if *s.cfg.Remote == "" {
|
||||||
|
err = fmt.Errorf("remote required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxServer) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
|
s.cfg = args.(MuxServerArgs)
|
||||||
|
if err = s.CheckArgs(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
s.sc = utils.NewServerChannel(host, p, s.log)
|
||||||
|
if *s.cfg.IsUDP {
|
||||||
|
err = s.sc.ListenUDP(func(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||||
|
s.udpChn <- MuxUDPItem{
|
||||||
|
packet: &packet,
|
||||||
|
localAddr: localAddr,
|
||||||
|
srcAddr: srcAddr,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("server on %s", (*s.sc.UDPListener).LocalAddr())
|
||||||
|
} else {
|
||||||
|
err = s.sc.ListenTCP(func(inConn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
s.log.Printf("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var outConn net.Conn
|
||||||
|
var ID string
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outConn, ID, err = s.GetOutConn()
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
s.log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.log.Printf("%s stream %s created", *s.cfg.Key, ID)
|
||||||
|
if *s.cfg.IsCompress {
|
||||||
|
die1 := make(chan bool, 1)
|
||||||
|
die2 := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
io.Copy(inConn, snappy.NewReader(outConn))
|
||||||
|
die1 <- true
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
io.Copy(snappy.NewWriter(outConn), inConn)
|
||||||
|
die2 <- true
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-die1:
|
||||||
|
case <-die2:
|
||||||
|
}
|
||||||
|
outConn.Close()
|
||||||
|
inConn.Close()
|
||||||
|
s.log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||||
|
} else {
|
||||||
|
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||||
|
s.log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||||
|
}, s.log)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("server on %s", (*s.sc.Listener).Addr())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) {
|
||||||
|
i := 1
|
||||||
|
if *s.cfg.SessionCount > 0 {
|
||||||
|
i = rand.Intn(*s.cfg.SessionCount)
|
||||||
|
}
|
||||||
|
outConn, err = s.GetConn(fmt.Sprintf("%d", i))
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("connection err: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remoteAddr := "tcp:" + *s.cfg.Remote
|
||||||
|
if *s.cfg.IsUDP {
|
||||||
|
remoteAddr = "udp:" + *s.cfg.Remote
|
||||||
|
}
|
||||||
|
ID = utils.Uniqueid()
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = outConn.Write(utils.BuildPacketData(ID, remoteAddr, s.cfg.Mgr.serverID))
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("write stream data err: %s ,retrying...", err)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) {
|
||||||
|
select {
|
||||||
|
case s.lockChn <- true:
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("can not connect at same time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
<-s.lockChn
|
||||||
|
}()
|
||||||
|
var session *smux.Session
|
||||||
|
_session, ok := s.sessions.Get(index)
|
||||||
|
if !ok {
|
||||||
|
var c net.Conn
|
||||||
|
c, err = s.getParentConn()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID))
|
||||||
|
c.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
session, err = smux.Client(c, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _sess, ok := s.sessions.Get(index); ok {
|
||||||
|
_sess.(*smux.Session).Close()
|
||||||
|
}
|
||||||
|
s.sessions.Set(index, session)
|
||||||
|
s.log.Printf("session[%s] created", index)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session.IsClosed() {
|
||||||
|
s.sessions.Remove(index)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
session = _session.(*smux.Session)
|
||||||
|
}
|
||||||
|
conn, err = session.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
s.sessions.Remove(index)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) getParentConn() (conn net.Conn, err error) {
|
||||||
|
if *s.cfg.ParentType == "tls" {
|
||||||
|
var _conn tls.Conn
|
||||||
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||||
|
if err == nil {
|
||||||
|
conn = net.Conn(&_conn)
|
||||||
|
}
|
||||||
|
} else if *s.cfg.ParentType == "kcp" {
|
||||||
|
conn, err = utils.ConnectKCPHost(*s.cfg.Parent, s.cfg.KCP)
|
||||||
|
} else {
|
||||||
|
conn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) UDPConnDeamon() {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
s.log.Printf("udp conn deamon crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var outConn net.Conn
|
||||||
|
var ID string
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item := <-s.udpChn
|
||||||
|
RETRY:
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if outConn == nil {
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outConn, ID, err = s.GetOutConn()
|
||||||
|
if err != nil {
|
||||||
|
outConn = nil
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
s.log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
go func(outConn net.Conn, ID string) {
|
||||||
|
if s.udpConn != nil {
|
||||||
|
(*s.udpConn).Close()
|
||||||
|
}
|
||||||
|
s.udpConn = &outConn
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn)
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body)
|
||||||
|
s.log.Printf("UDP deamon connection %s exited", ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//s.log.Printf("udp packet revecived over parent , local:%s", srcAddrFromConn)
|
||||||
|
_srcAddr := strings.Split(srcAddrFromConn, ":")
|
||||||
|
if len(_srcAddr) != 2 {
|
||||||
|
s.log.Printf("parse revecived udp packet fail, addr error : %s", srcAddrFromConn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
port, _ := strconv.Atoi(_srcAddr[1])
|
||||||
|
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
|
||||||
|
s.sc.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
|
||||||
|
s.sc.UDPListener.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("udp response to local %s fail,ERR:%s", srcAddrFromConn, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//s.log.Printf("udp response to local %s success , %v", srcAddrFromConn, body)
|
||||||
|
}
|
||||||
|
}(outConn, ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outConn.SetWriteDeadline(time.Now().Add(time.Second))
|
||||||
|
_, err = outConn.Write(utils.UDPPacket(item.srcAddr.String(), *item.packet))
|
||||||
|
outConn.SetWriteDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
outConn = nil
|
||||||
|
s.log.Printf("write udp packet to %s fail ,flush err:%s ,retrying...", *s.cfg.Parent, err)
|
||||||
|
goto RETRY
|
||||||
|
}
|
||||||
|
//s.log.Printf("write packet %v", *item.packet)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@ -2,50 +2,62 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
logger "log"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start(args interface{}) (err error)
|
Start(args interface{}, log *logger.Logger) (err error)
|
||||||
Clean()
|
Clean()
|
||||||
}
|
}
|
||||||
type ServiceItem struct {
|
type ServiceItem struct {
|
||||||
S Service
|
S Service
|
||||||
Args interface{}
|
Args interface{}
|
||||||
Name string
|
Name string
|
||||||
|
Log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
var servicesMap = map[string]*ServiceItem{}
|
var servicesMap = map[string]*ServiceItem{}
|
||||||
|
|
||||||
func Regist(name string, s Service, args interface{}) {
|
func Regist(name string, s Service, args interface{}, log *logger.Logger) {
|
||||||
|
Stop(name)
|
||||||
servicesMap[name] = &ServiceItem{
|
servicesMap[name] = &ServiceItem{
|
||||||
S: s,
|
S: s,
|
||||||
Args: args,
|
Args: args,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
Log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func Run(name string, args ...interface{}) (service *ServiceItem, err error) {
|
func GetService(name string) *ServiceItem {
|
||||||
|
if s, ok := servicesMap[name]; ok && s.S != nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func Stop(name string) {
|
||||||
|
if s, ok := servicesMap[name]; ok && s.S != nil {
|
||||||
|
s.S.Clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Run(name string, args interface{}) (service *ServiceItem, err error) {
|
||||||
service, ok := servicesMap[name]
|
service, ok := servicesMap[name]
|
||||||
if ok {
|
if ok {
|
||||||
go func() {
|
defer func() {
|
||||||
defer func() {
|
e := recover()
|
||||||
err := recover()
|
if e != nil {
|
||||||
if err != nil {
|
err = fmt.Errorf("%s servcie crashed, ERR: %s\ntrace:%s", name, e, string(debug.Stack()))
|
||||||
log.Fatalf("%s servcie crashed, ERR: %s\ntrace:%s", name, err, string(debug.Stack()))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if len(args) == 1 {
|
|
||||||
err = service.S.Start(args[0])
|
|
||||||
} else {
|
|
||||||
err = service.S.Start(service.Args)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%s servcie fail, ERR: %s", name, err)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
if args != nil {
|
||||||
if !ok {
|
err = service.S.Start(args, service.Log)
|
||||||
|
} else {
|
||||||
|
err = service.S.Start(service.Args, service.Log)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("%s servcie fail, ERR: %s", name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
err = fmt.Errorf("service %s not found", name)
|
err = fmt.Errorf("service %s not found", name)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@ -4,25 +4,32 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
|
||||||
"proxy/utils/aes"
|
|
||||||
"proxy/utils/socks"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
"github.com/snail007/goproxy/utils/aes"
|
||||||
|
"github.com/snail007/goproxy/utils/conncrypt"
|
||||||
|
"github.com/snail007/goproxy/utils/socks"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Socks struct {
|
type Socks struct {
|
||||||
cfg SocksArgs
|
cfg SocksArgs
|
||||||
checker utils.Checker
|
checker utils.Checker
|
||||||
basicAuth utils.BasicAuth
|
basicAuth utils.BasicAuth
|
||||||
sshClient *ssh.Client
|
sshClient *ssh.Client
|
||||||
lockChn chan bool
|
lockChn chan bool
|
||||||
udpSC utils.ServerChannel
|
udpSC utils.ServerChannel
|
||||||
|
sc *utils.ServerChannel
|
||||||
|
domainResolver utils.DomainResolver
|
||||||
|
isStop bool
|
||||||
|
userConns utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks() Service {
|
func NewSocks() Service {
|
||||||
@ -31,27 +38,50 @@ func NewSocks() Service {
|
|||||||
checker: utils.Checker{},
|
checker: utils.Checker{},
|
||||||
basicAuth: utils.BasicAuth{},
|
basicAuth: utils.BasicAuth{},
|
||||||
lockChn: make(chan bool, 1),
|
lockChn: make(chan bool, 1),
|
||||||
|
isStop: false,
|
||||||
|
userConns: utils.NewConcurrentMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Socks) CheckArgs() {
|
func (s *Socks) CheckArgs() (err error) {
|
||||||
var err error
|
|
||||||
if *s.cfg.LocalType == "tls" {
|
if *s.cfg.LocalType == "tls" || (*s.cfg.Parent != "" && *s.cfg.ParentType == "tls") {
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.CaCertFile != "" {
|
||||||
|
s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read ca file error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if *s.cfg.Parent != "" {
|
if *s.cfg.Parent != "" {
|
||||||
if *s.cfg.ParentType == "" {
|
if *s.cfg.ParentType == "" {
|
||||||
log.Fatalf("parent type unkown,use -T <tls|tcp|ssh>")
|
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|ssh|kcp>")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "tls" {
|
host, _, e := net.SplitHostPort(*s.cfg.Parent)
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
if e != nil {
|
||||||
|
err = fmt.Errorf("parent format error : %s", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.UDPParent == "" {
|
||||||
|
*s.cfg.UDPParent = net.JoinHostPort(host, "33090")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(*s.cfg.UDPParent, ":") {
|
||||||
|
*s.cfg.UDPParent = net.JoinHostPort(host, strings.TrimLeft(*s.cfg.UDPParent, ":"))
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "ssh" {
|
if *s.cfg.ParentType == "ssh" {
|
||||||
if *s.cfg.SSHUser == "" {
|
if *s.cfg.SSHUser == "" {
|
||||||
log.Fatalf("ssh user required")
|
err = fmt.Errorf("ssh user required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||||
log.Fatalf("ssh password or key required")
|
err = fmt.Errorf("ssh password or key required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.SSHPassword != "" {
|
if *s.cfg.SSHPassword != "" {
|
||||||
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
||||||
@ -59,7 +89,8 @@ func (s *Socks) CheckArgs() {
|
|||||||
var SSHSigner ssh.Signer
|
var SSHSigner ssh.Signer
|
||||||
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("read key file ERR: %s", err)
|
err = fmt.Errorf("read key file ERR: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.SSHKeyFileSalt != "" {
|
if *s.cfg.SSHKeyFileSalt != "" {
|
||||||
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||||
@ -67,34 +98,44 @@ func (s *Socks) CheckArgs() {
|
|||||||
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("parse ssh private key fail,ERR: %s", err)
|
err = fmt.Errorf("parse ssh private key fail,ERR: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *Socks) InitService() {
|
func (s *Socks) InitService() (err error) {
|
||||||
s.InitBasicAuth()
|
s.InitBasicAuth()
|
||||||
s.checker = utils.NewChecker(*s.cfg.Timeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct)
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
(*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
|
||||||
|
}
|
||||||
|
s.checker = utils.NewChecker(*s.cfg.Timeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct, s.log)
|
||||||
if *s.cfg.ParentType == "ssh" {
|
if *s.cfg.ParentType == "ssh" {
|
||||||
err := s.ConnectSSH()
|
e := s.ConnectSSH()
|
||||||
if err != nil {
|
if e != nil {
|
||||||
log.Fatalf("init service fail, ERR: %s", err)
|
err = fmt.Errorf("init service fail, ERR: %s", e)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
//循环检查ssh网络连通性
|
//循环检查ssh网络连通性
|
||||||
for {
|
for {
|
||||||
conn, err := utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout*2)
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = conn.Write([]byte{0})
|
_, err = conn.Write([]byte{0})
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.sshClient != nil {
|
if s.sshClient != nil {
|
||||||
s.sshClient.Close()
|
s.sshClient.Close()
|
||||||
}
|
}
|
||||||
log.Printf("ssh offline, retrying...")
|
s.log.Printf("ssh offline, retrying...")
|
||||||
s.ConnectSSH()
|
s.ConnectSSH()
|
||||||
} else {
|
} else {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -104,45 +145,71 @@ func (s *Socks) InitService() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "ssh" {
|
if *s.cfg.ParentType == "ssh" {
|
||||||
log.Println("warn: socks udp not suppored for ssh")
|
s.log.Printf("warn: socks udp not suppored for ssh")
|
||||||
} else {
|
} else {
|
||||||
|
s.udpSC = utils.NewServerChannelHost(*s.cfg.UDPLocal, s.log)
|
||||||
s.udpSC = utils.NewServerChannelHost(*s.cfg.UDPLocal)
|
e := s.udpSC.ListenUDP(s.udpCallback)
|
||||||
err := s.udpSC.ListenUDP(s.udpCallback)
|
if e != nil {
|
||||||
if err != nil {
|
err = fmt.Errorf("init udp service fail, ERR: %s", e)
|
||||||
log.Fatalf("init udp service fail, ERR: %s", err)
|
return
|
||||||
}
|
}
|
||||||
log.Printf("udp socks proxy on %s", s.udpSC.UDPListener.LocalAddr())
|
s.log.Printf("udp socks proxy on %s", s.udpSC.UDPListener.LocalAddr())
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *Socks) StopService() {
|
func (s *Socks) StopService() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop socks service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service socks stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
s.checker.Stop()
|
||||||
if s.sshClient != nil {
|
if s.sshClient != nil {
|
||||||
s.sshClient.Close()
|
s.sshClient.Close()
|
||||||
}
|
}
|
||||||
if s.udpSC.UDPListener != nil {
|
if s.udpSC.UDPListener != nil {
|
||||||
s.udpSC.UDPListener.Close()
|
s.udpSC.UDPListener.Close()
|
||||||
}
|
}
|
||||||
|
if s.sc != nil && (*s.sc).Listener != nil {
|
||||||
|
(*(*s.sc).Listener).Close()
|
||||||
|
}
|
||||||
|
for _, c := range s.userConns.Items() {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func (s *Socks) Start(args interface{}) (err error) {
|
func (s *Socks) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
//start()
|
//start()
|
||||||
s.cfg = args.(SocksArgs)
|
s.cfg = args.(SocksArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
s.InitService()
|
return
|
||||||
if *s.cfg.Parent != "" {
|
|
||||||
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
|
||||||
}
|
}
|
||||||
sc := utils.NewServerChannelHost(*s.cfg.Local)
|
if err = s.InitService(); err != nil {
|
||||||
|
s.InitService()
|
||||||
|
}
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
}
|
||||||
|
if *s.cfg.UDPParent != "" {
|
||||||
|
s.log.Printf("use socks udp parent %s", *s.cfg.UDPParent)
|
||||||
|
}
|
||||||
|
sc := utils.NewServerChannelHost(*s.cfg.Local, s.log)
|
||||||
if *s.cfg.LocalType == TYPE_TCP {
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
err = sc.ListenTCP(s.socksConnCallback)
|
err = sc.ListenTCP(s.socksConnCallback)
|
||||||
} else if *s.cfg.LocalType == TYPE_TLS {
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.socksConnCallback)
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.socksConnCallback)
|
||||||
} else if *s.cfg.LocalType == TYPE_KCP {
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.socksConnCallback)
|
err = sc.ListenKCP(s.cfg.KCP, s.socksConnCallback, s.log)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("%s socks proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
s.sc = &sc
|
||||||
|
s.log.Printf("%s socks proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *Socks) Clean() {
|
func (s *Socks) Clean() {
|
||||||
@ -158,29 +225,29 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
|
|||||||
//decode b
|
//decode b
|
||||||
rawB, err = goaes.Decrypt(s.UDPKey(), b)
|
rawB, err = goaes.Decrypt(s.UDPKey(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("decrypt udp packet fail from %s", srcAddr.String())
|
s.log.Printf("decrypt udp packet fail from %s", srcAddr.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p, err := socks.ParseUDPPacket(rawB)
|
p, err := socks.ParseUDPPacket(rawB)
|
||||||
log.Printf("udp revecived:%v", len(p.Data()))
|
s.log.Printf("udp revecived:%v", len(p.Data()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("parse udp packet fail, ERR:%s", err)
|
s.log.Printf("parse udp packet fail, ERR:%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//防止死循环
|
//防止死循环
|
||||||
if s.IsDeadLoop((*localAddr).String(), p.Host()) {
|
if s.IsDeadLoop((*localAddr).String(), p.Host()) {
|
||||||
log.Printf("dead loop detected , %s", p.Host())
|
s.log.Printf("dead loop detected , %s", p.Host())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("##########udp to -> %s:%s###########", p.Host(), p.Port())
|
//s.log.Printf("##########udp to -> %s:%s###########", p.Host(), p.Port())
|
||||||
if *s.cfg.Parent != "" {
|
if *s.cfg.Parent != "" {
|
||||||
//有上级代理,转发给上级
|
//有上级代理,转发给上级
|
||||||
if *s.cfg.ParentType == "tls" {
|
if *s.cfg.ParentType == "tls" {
|
||||||
//encode b
|
//encode b
|
||||||
rawB, err = goaes.Encrypt(s.UDPKey(), rawB)
|
rawB, err = goaes.Encrypt(s.UDPKey(), rawB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("encrypt udp data fail to %s", *s.cfg.Parent)
|
s.log.Printf("encrypt udp data fail to %s", *s.cfg.Parent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,42 +255,45 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
|
|||||||
if parent == "" {
|
if parent == "" {
|
||||||
parent = *s.cfg.Parent
|
parent = *s.cfg.Parent
|
||||||
}
|
}
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp", parent)
|
dstAddr, err := net.ResolveUDPAddr("udp", s.Resolve(parent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("can't resolve address: %s", err)
|
s.log.Printf("can't resolve address: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*5)))
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*5)))
|
||||||
_, err = conn.Write(rawB)
|
_, err = conn.Write(rawB)
|
||||||
log.Printf("udp request:%v", len(rawB))
|
conn.SetDeadline(time.Time{})
|
||||||
|
s.log.Printf("udp request:%v", len(rawB))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("send udp packet to %s success", dstAddr.String())
|
//s.log.Printf("send udp packet to %s success", dstAddr.String())
|
||||||
buf := make([]byte, 10*1024)
|
buf := make([]byte, 10*1024)
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
length, _, err := conn.ReadFromUDP(buf)
|
length, _, err := conn.ReadFromUDP(buf)
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respBody := buf[0:length]
|
respBody := buf[0:length]
|
||||||
log.Printf("udp response:%v", len(respBody))
|
s.log.Printf("udp response:%v", len(respBody))
|
||||||
//log.Printf("revecived udp packet from %s", dstAddr.String())
|
//s.log.Printf("revecived udp packet from %s", dstAddr.String())
|
||||||
if *s.cfg.ParentType == "tls" {
|
if *s.cfg.ParentType == "tls" {
|
||||||
//decode b
|
//decode b
|
||||||
respBody, err = goaes.Decrypt(s.UDPKey(), respBody)
|
respBody, err = goaes.Decrypt(s.UDPKey(), respBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("encrypt udp data fail to %s", *s.cfg.Parent)
|
s.log.Printf("encrypt udp data fail to %s", *s.cfg.Parent)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -231,72 +301,94 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
|
|||||||
if *s.cfg.LocalType == "tls" {
|
if *s.cfg.LocalType == "tls" {
|
||||||
d, err := goaes.Encrypt(s.UDPKey(), respBody)
|
d, err := goaes.Encrypt(s.UDPKey(), respBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("encrypt udp data fail from %s", dstAddr.String())
|
s.log.Printf("encrypt udp data fail from %s", dstAddr.String())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
||||||
log.Printf("udp reply:%v", len(d))
|
s.udpSC.UDPListener.SetDeadline(time.Time{})
|
||||||
|
s.log.Printf("udp reply:%v", len(d))
|
||||||
|
d = nil
|
||||||
} else {
|
} else {
|
||||||
|
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
s.udpSC.UDPListener.WriteToUDP(respBody, srcAddr)
|
s.udpSC.UDPListener.WriteToUDP(respBody, srcAddr)
|
||||||
log.Printf("udp reply:%v", len(respBody))
|
s.udpSC.UDPListener.SetDeadline(time.Time{})
|
||||||
|
s.log.Printf("udp reply:%v", len(respBody))
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//本地代理
|
//本地代理
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(p.Host(), p.Port()))
|
dstAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(s.Resolve(p.Host()), p.Port()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("can't resolve address: %s", err)
|
s.log.Printf("can't resolve address: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*3)))
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*3)))
|
||||||
_, err = conn.Write(p.Data())
|
_, err = conn.Write(p.Data())
|
||||||
log.Printf("udp send:%v", len(p.Data()))
|
conn.SetDeadline(time.Time{})
|
||||||
|
s.log.Printf("udp send:%v", len(p.Data()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("send udp packet to %s success", dstAddr.String())
|
//s.log.Printf("send udp packet to %s success", dstAddr.String())
|
||||||
buf := make([]byte, 10*1024)
|
buf := make([]byte, 10*1024)
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
length, _, err := conn.ReadFromUDP(buf)
|
length, _, err := conn.ReadFromUDP(buf)
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respBody := buf[0:length]
|
respBody := buf[0:length]
|
||||||
//封装来自真实服务器的数据,返回给访问者
|
//封装来自真实服务器的数据,返回给访问者
|
||||||
respPacket := p.NewReply(respBody)
|
respPacket := p.NewReply(respBody)
|
||||||
//log.Printf("revecived udp packet from %s", dstAddr.String())
|
//s.log.Printf("revecived udp packet from %s", dstAddr.String())
|
||||||
if *s.cfg.LocalType == "tls" {
|
if *s.cfg.LocalType == "tls" {
|
||||||
d, err := goaes.Encrypt(s.UDPKey(), respPacket)
|
d, err := goaes.Encrypt(s.UDPKey(), respPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("encrypt udp data fail from %s", dstAddr.String())
|
s.log.Printf("encrypt udp data fail from %s", dstAddr.String())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
||||||
|
s.udpSC.UDPListener.SetDeadline(time.Time{})
|
||||||
|
d = nil
|
||||||
} else {
|
} else {
|
||||||
|
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
s.udpSC.UDPListener.WriteToUDP(respPacket, srcAddr)
|
s.udpSC.UDPListener.WriteToUDP(respPacket, srcAddr)
|
||||||
|
s.udpSC.UDPListener.SetDeadline(time.Time{})
|
||||||
}
|
}
|
||||||
log.Printf("udp reply:%v", len(respPacket))
|
s.log.Printf("udp reply:%v", len(respPacket))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
func (s *Socks) socksConnCallback(inConn net.Conn) {
|
func (s *Socks) socksConnCallback(inConn net.Conn) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("socks conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
s.log.Printf("socks conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
inConn.Close()
|
inConn.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if *s.cfg.LocalCompress {
|
||||||
|
inConn = utils.NewCompConn(inConn)
|
||||||
|
}
|
||||||
|
if *s.cfg.LocalKey != "" {
|
||||||
|
inConn = conncrypt.New(inConn, &conncrypt.Config{
|
||||||
|
Password: *s.cfg.LocalKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
//协商开始
|
//协商开始
|
||||||
|
|
||||||
//method select request
|
//method select request
|
||||||
@ -306,7 +398,7 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
log.Printf("new methods request fail,ERR: %s", err)
|
s.log.Printf("new methods request fail,ERR: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,29 +406,29 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
|
|||||||
if !methodReq.Select(socks.Method_NO_AUTH) {
|
if !methodReq.Select(socks.Method_NO_AUTH) {
|
||||||
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
log.Printf("none method found : Method_NO_AUTH")
|
s.log.Printf("none method found : Method_NO_AUTH")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//method select reply
|
//method select reply
|
||||||
err = methodReq.Reply(socks.Method_NO_AUTH)
|
err = methodReq.Reply(socks.Method_NO_AUTH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("reply answer data fail,ERR: %s", err)
|
s.log.Printf("reply answer data fail,ERR: %s", err)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// log.Printf("% x", methodReq.Bytes())
|
// s.log.Printf("% x", methodReq.Bytes())
|
||||||
} else {
|
} else {
|
||||||
//auth
|
//auth
|
||||||
if !methodReq.Select(socks.Method_USER_PASS) {
|
if !methodReq.Select(socks.Method_USER_PASS) {
|
||||||
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
log.Printf("none method found : Method_USER_PASS")
|
s.log.Printf("none method found : Method_USER_PASS")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//method reply need auth
|
//method reply need auth
|
||||||
err = methodReq.Reply(socks.Method_USER_PASS)
|
err = methodReq.Reply(socks.Method_USER_PASS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("reply answer data fail,ERR: %s", err)
|
s.log.Printf("reply answer data fail,ERR: %s", err)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -352,13 +444,19 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
|
|||||||
r := buf[:n]
|
r := buf[:n]
|
||||||
user := string(r[2 : r[1]+2])
|
user := string(r[2 : r[1]+2])
|
||||||
pass := string(r[2+r[1]+1:])
|
pass := string(r[2+r[1]+1:])
|
||||||
//log.Printf("user:%s,pass:%s", user, pass)
|
//s.log.Printf("user:%s,pass:%s", user, pass)
|
||||||
//auth
|
//auth
|
||||||
_addr := strings.Split(inConn.RemoteAddr().String(), ":")
|
_addr := strings.Split(inConn.RemoteAddr().String(), ":")
|
||||||
if s.basicAuth.CheckUserPass(user, pass, _addr[0], "") {
|
if s.basicAuth.CheckUserPass(user, pass, _addr[0], "") {
|
||||||
|
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
inConn.Write([]byte{0x01, 0x00})
|
inConn.Write([]byte{0x01, 0x00})
|
||||||
|
inConn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
inConn.Write([]byte{0x01, 0x01})
|
inConn.Write([]byte{0x01, 0x01})
|
||||||
|
inConn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -367,7 +465,7 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
|
|||||||
//request detail
|
//request detail
|
||||||
request, err := socks.NewRequest(inConn)
|
request, err := socks.NewRequest(inConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("read request data fail,ERR: %s", err)
|
s.log.Printf("read request data fail,ERR: %s", err)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -395,7 +493,7 @@ func (s *Socks) proxyUDP(inConn *net.Conn, methodReq socks.MethodsRequest, reque
|
|||||||
}
|
}
|
||||||
host, _, _ := net.SplitHostPort((*inConn).LocalAddr().String())
|
host, _, _ := net.SplitHostPort((*inConn).LocalAddr().String())
|
||||||
_, port, _ := net.SplitHostPort(s.udpSC.UDPListener.LocalAddr().String())
|
_, port, _ := net.SplitHostPort(s.udpSC.UDPListener.LocalAddr().String())
|
||||||
log.Printf("proxy udp on %s", net.JoinHostPort(host, port))
|
s.log.Printf("proxy udp on %s", net.JoinHostPort(host, port))
|
||||||
request.UDPReply(socks.REP_SUCCESS, net.JoinHostPort(host, port))
|
request.UDPReply(socks.REP_SUCCESS, net.JoinHostPort(host, port))
|
||||||
}
|
}
|
||||||
func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, request socks.Request) {
|
func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, request socks.Request) {
|
||||||
@ -407,24 +505,34 @@ func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, reque
|
|||||||
//防止死循环
|
//防止死循环
|
||||||
if s.IsDeadLoop((*inConn).LocalAddr().String(), request.Host()) {
|
if s.IsDeadLoop((*inConn).LocalAddr().String(), request.Host()) {
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
log.Printf("dead loop detected , %s", request.Host())
|
s.log.Printf("dead loop detected , %s", request.Host())
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
if *s.cfg.Always {
|
if *s.cfg.Always {
|
||||||
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
||||||
} else {
|
} else {
|
||||||
if *s.cfg.Parent != "" {
|
if *s.cfg.Parent != "" {
|
||||||
s.checker.Add(request.Addr(), true, "", "", nil)
|
host, _, _ := net.SplitHostPort(request.Addr())
|
||||||
useProxy, _, _ = s.checker.IsBlocked(request.Addr())
|
useProxy := false
|
||||||
|
if utils.IsIternalIP(host, *s.cfg.Always) {
|
||||||
|
useProxy = false
|
||||||
|
} else {
|
||||||
|
k := s.Resolve(request.Addr())
|
||||||
|
s.checker.Add(k)
|
||||||
|
useProxy, _, _ = s.checker.IsBlocked(k)
|
||||||
|
}
|
||||||
if useProxy {
|
if useProxy {
|
||||||
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
||||||
} else {
|
} else {
|
||||||
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
|
outConn, err = utils.ConnectHost(s.Resolve(request.Addr()), *s.cfg.Timeout)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
|
outConn, err = utils.ConnectHost(s.Resolve(request.Addr()), *s.cfg.Timeout)
|
||||||
useProxy = false
|
useProxy = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,25 +540,32 @@ func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, reque
|
|||||||
if err == nil || tryCount > maxTryCount || *s.cfg.Parent == "" {
|
if err == nil || tryCount > maxTryCount || *s.cfg.Parent == "" {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
log.Printf("get out conn fail,%s,retrying...", err)
|
s.log.Printf("get out conn fail,%s,retrying...", err)
|
||||||
time.Sleep(time.Second * 2)
|
time.Sleep(time.Second * 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("get out conn fail,%s", err)
|
s.log.Printf("get out conn fail,%s", err)
|
||||||
request.TCPReply(socks.REP_NETWOR_UNREACHABLE)
|
request.TCPReply(socks.REP_NETWOR_UNREACHABLE)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("use proxy %v : %s", useProxy, request.Addr())
|
|
||||||
|
s.log.Printf("use proxy %v : %s", useProxy, request.Addr())
|
||||||
|
|
||||||
request.TCPReply(socks.REP_SUCCESS)
|
request.TCPReply(socks.REP_SUCCESS)
|
||||||
inAddr := (*inConn).RemoteAddr().String()
|
inAddr := (*inConn).RemoteAddr().String()
|
||||||
//inLocalAddr := (*inConn).LocalAddr().String()
|
//inLocalAddr := (*inConn).LocalAddr().String()
|
||||||
|
|
||||||
log.Printf("conn %s - %s connected", inAddr, request.Addr())
|
s.log.Printf("conn %s - %s connected", inAddr, request.Addr())
|
||||||
utils.IoBind(*inConn, outConn, func(err interface{}) {
|
utils.IoBind(*inConn, outConn, func(err interface{}) {
|
||||||
log.Printf("conn %s - %s released", inAddr, request.Addr())
|
s.log.Printf("conn %s - %s released", inAddr, request.Addr())
|
||||||
})
|
s.userConns.Remove(inAddr)
|
||||||
|
}, s.log)
|
||||||
|
if c, ok := s.userConns.Get(inAddr); ok {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
s.userConns.Remove(inAddr)
|
||||||
|
}
|
||||||
|
s.userConns.Set(inAddr, inConn)
|
||||||
}
|
}
|
||||||
func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn net.Conn, err interface{}) {
|
func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn net.Conn, err interface{}) {
|
||||||
switch *s.cfg.ParentType {
|
switch *s.cfg.ParentType {
|
||||||
@ -461,50 +576,65 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n
|
|||||||
case "tcp":
|
case "tcp":
|
||||||
if *s.cfg.ParentType == "tls" {
|
if *s.cfg.ParentType == "tls" {
|
||||||
var _outConn tls.Conn
|
var _outConn tls.Conn
|
||||||
_outConn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
_outConn, err = utils.TlsConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||||
outConn = net.Conn(&_outConn)
|
outConn = net.Conn(&_outConn)
|
||||||
} else if *s.cfg.ParentType == "kcp" {
|
} else if *s.cfg.ParentType == "kcp" {
|
||||||
outConn, err = utils.ConnectKCPHost(*s.cfg.Parent, *s.cfg.KCPMethod, *s.cfg.KCPKey)
|
outConn, err = utils.ConnectKCPHost(s.Resolve(*s.cfg.Parent), s.cfg.KCP)
|
||||||
} else {
|
} else {
|
||||||
outConn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
outConn, err = utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("connect fail,%s", err)
|
err = fmt.Errorf("connect fail,%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if *s.cfg.ParentCompress {
|
||||||
|
outConn = utils.NewCompConn(outConn)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentKey != "" {
|
||||||
|
outConn = conncrypt.New(outConn, &conncrypt.Config{
|
||||||
|
Password: *s.cfg.ParentKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
var buf = make([]byte, 1024)
|
var buf = make([]byte, 1024)
|
||||||
//var n int
|
//var n int
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = outConn.Write(methodBytes)
|
_, err = outConn.Write(methodBytes)
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("write method fail,%s", err)
|
err = fmt.Errorf("write method fail,%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = outConn.Read(buf)
|
_, err = outConn.Read(buf)
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("read method reply fail,%s", err)
|
err = fmt.Errorf("read method reply fail,%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//resp := buf[:n]
|
//resp := buf[:n]
|
||||||
//log.Printf("resp:%v", resp)
|
//s.log.Printf("resp:%v", resp)
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = outConn.Write(reqBytes)
|
_, err = outConn.Write(reqBytes)
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("write req detail fail,%s", err)
|
err = fmt.Errorf("write req detail fail,%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// _, err = outConn.Read(buf)
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
// if err != nil {
|
_, err = outConn.Read(buf)
|
||||||
// err = fmt.Errorf("read req reply fail,%s", err)
|
outConn.SetDeadline(time.Time{})
|
||||||
// return
|
if err != nil {
|
||||||
// }
|
err = fmt.Errorf("read req reply fail,%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
//result := buf[:n]
|
//result := buf[:n]
|
||||||
//log.Printf("result:%v", result)
|
//s.log.Printf("result:%v", result)
|
||||||
|
|
||||||
case "ssh":
|
case "ssh":
|
||||||
maxTryCount := 1
|
maxTryCount := 1
|
||||||
tryCount := 0
|
tryCount := 0
|
||||||
RETRY:
|
RETRY:
|
||||||
if tryCount >= maxTryCount {
|
if tryCount >= maxTryCount || s.isStop {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wait := make(chan bool, 1)
|
wait := make(chan bool, 1)
|
||||||
@ -524,7 +654,7 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n
|
|||||||
s.sshClient.Close()
|
s.sshClient.Close()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
s.log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||||
e := s.ConnectSSH()
|
e := s.ConnectSSH()
|
||||||
if e == nil {
|
if e == nil {
|
||||||
tryCount++
|
tryCount++
|
||||||
@ -556,15 +686,19 @@ func (s *Socks) ConnectSSH() (err error) {
|
|||||||
if s.sshClient != nil {
|
if s.sshClient != nil {
|
||||||
s.sshClient.Close()
|
s.sshClient.Close()
|
||||||
}
|
}
|
||||||
s.sshClient, err = ssh.Dial("tcp", *s.cfg.Parent, &config)
|
s.sshClient, err = ssh.Dial("tcp", s.Resolve(*s.cfg.Parent), &config)
|
||||||
<-s.lockChn
|
<-s.lockChn
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *Socks) InitBasicAuth() (err error) {
|
func (s *Socks) InitBasicAuth() (err error) {
|
||||||
s.basicAuth = utils.NewBasicAuth()
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
s.basicAuth = utils.NewBasicAuth(&(*s).domainResolver, s.log)
|
||||||
|
} else {
|
||||||
|
s.basicAuth = utils.NewBasicAuth(nil, s.log)
|
||||||
|
}
|
||||||
if *s.cfg.AuthURL != "" {
|
if *s.cfg.AuthURL != "" {
|
||||||
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||||
log.Printf("auth from %s", *s.cfg.AuthURL)
|
s.log.Printf("auth from %s", *s.cfg.AuthURL)
|
||||||
}
|
}
|
||||||
if *s.cfg.AuthFile != "" {
|
if *s.cfg.AuthFile != "" {
|
||||||
var n = 0
|
var n = 0
|
||||||
@ -573,11 +707,11 @@ func (s *Socks) InitBasicAuth() (err error) {
|
|||||||
err = fmt.Errorf("auth-file ERR:%s", err)
|
err = fmt.Errorf("auth-file ERR:%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
s.log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
||||||
}
|
}
|
||||||
if len(*s.cfg.Auth) > 0 {
|
if len(*s.cfg.Auth) > 0 {
|
||||||
n := s.basicAuth.Add(*s.cfg.Auth)
|
n := s.basicAuth.Add(*s.cfg.Auth)
|
||||||
log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
s.log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -595,7 +729,11 @@ func (s *Socks) IsDeadLoop(inLocalAddr string, host string) bool {
|
|||||||
}
|
}
|
||||||
if inPort == outPort {
|
if inPort == outPort {
|
||||||
var outIPs []net.IP
|
var outIPs []net.IP
|
||||||
outIPs, err = net.LookupIP(outDomain)
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
outIPs = []net.IP{net.ParseIP(s.Resolve(outDomain))}
|
||||||
|
} else {
|
||||||
|
outIPs, err = net.LookupIP(outDomain)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, ip := range outIPs {
|
for _, ip := range outIPs {
|
||||||
if ip.String() == inIP {
|
if ip.String() == inIP {
|
||||||
@ -619,3 +757,13 @@ func (s *Socks) IsDeadLoop(inLocalAddr string, host string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
func (s *Socks) Resolve(address string) string {
|
||||||
|
if *s.cfg.DNSAddress == "" {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
ip, err := s.domainResolver.Resolve(address)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("dns error %s , ERR:%s", address, err)
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|||||||
467
services/sps.go
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
logger "log"
|
||||||
|
"net"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
"github.com/snail007/goproxy/utils/conncrypt"
|
||||||
|
"github.com/snail007/goproxy/utils/socks"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SPS struct {
|
||||||
|
outPool utils.OutConn
|
||||||
|
cfg SPSArgs
|
||||||
|
domainResolver utils.DomainResolver
|
||||||
|
basicAuth utils.BasicAuth
|
||||||
|
serverChannels []*utils.ServerChannel
|
||||||
|
userConns utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSPS() Service {
|
||||||
|
return &SPS{
|
||||||
|
outPool: utils.OutConn{},
|
||||||
|
cfg: SPSArgs{},
|
||||||
|
basicAuth: utils.BasicAuth{},
|
||||||
|
serverChannels: []*utils.ServerChannel{},
|
||||||
|
userConns: utils.NewConcurrentMap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *SPS) CheckArgs() (err error) {
|
||||||
|
if *s.cfg.Parent == "" {
|
||||||
|
err = fmt.Errorf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "" {
|
||||||
|
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|kcp>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.CaCertFile != "" {
|
||||||
|
s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read ca file error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SPS) InitService() (err error) {
|
||||||
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
(*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
|
||||||
|
}
|
||||||
|
s.InitOutConnPool()
|
||||||
|
err = s.InitBasicAuth()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SPS) InitOutConnPool() {
|
||||||
|
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP {
|
||||||
|
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
||||||
|
//parent string, timeout int, InitialCap int, MaxCap int
|
||||||
|
s.outPool = utils.NewOutConn(
|
||||||
|
0,
|
||||||
|
*s.cfg.ParentType,
|
||||||
|
s.cfg.KCP,
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes,
|
||||||
|
*s.cfg.Parent,
|
||||||
|
*s.cfg.Timeout,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPS) StopService() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop sps service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service sps stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, sc := range s.serverChannels {
|
||||||
|
if sc.Listener != nil && *sc.Listener != nil {
|
||||||
|
(*sc.Listener).Close()
|
||||||
|
}
|
||||||
|
if sc.UDPListener != nil {
|
||||||
|
(*sc.UDPListener).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range s.userConns.Items() {
|
||||||
|
if _, ok := c.(*net.Conn); ok {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
if _, ok := c.(**net.Conn); ok {
|
||||||
|
(*(*c.(**net.Conn))).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *SPS) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
|
s.cfg = args.(SPSArgs)
|
||||||
|
if err = s.CheckArgs(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("use %s %s parent %s", *s.cfg.ParentType, *s.cfg.ParentServiceType, *s.cfg.Parent)
|
||||||
|
for _, addr := range strings.Split(*s.cfg.Local, ",") {
|
||||||
|
if addr != "" {
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p, s.log)
|
||||||
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
|
err = sc.ListenTCP(s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
|
err = sc.ListenKCP(s.cfg.KCP, s.callback, s.log)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("%s http(s)+socks proxy on %s", s.cfg.Protocol(), (*sc.Listener).Addr())
|
||||||
|
s.serverChannels = append(s.serverChannels, &sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPS) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *SPS) callback(inConn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
s.log.Printf("%s conn handler crashed with err : %s \nstack: %s", s.cfg.Protocol(), err, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if *s.cfg.LocalCompress {
|
||||||
|
inConn = utils.NewCompConn(inConn)
|
||||||
|
}
|
||||||
|
if *s.cfg.LocalKey != "" {
|
||||||
|
inConn = conncrypt.New(inConn, &conncrypt.Config{
|
||||||
|
Password: *s.cfg.LocalKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch *s.cfg.ParentType {
|
||||||
|
case TYPE_KCP:
|
||||||
|
fallthrough
|
||||||
|
case TYPE_TCP:
|
||||||
|
fallthrough
|
||||||
|
case TYPE_TLS:
|
||||||
|
err = s.OutToTCP(&inConn)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("connect to %s parent %s fail, ERR:%s from %s", *s.cfg.ParentType, *s.cfg.Parent, err, inConn.RemoteAddr())
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *SPS) OutToTCP(inConn *net.Conn) (err error) {
|
||||||
|
bInConn := utils.NewBufferedConn(*inConn)
|
||||||
|
//important
|
||||||
|
//action read will regist read event to system,
|
||||||
|
//when data arrived , system call process
|
||||||
|
//so that we can get buffered bytes count
|
||||||
|
//otherwise Buffered() always return 0
|
||||||
|
bInConn.ReadByte()
|
||||||
|
bInConn.UnreadByte()
|
||||||
|
|
||||||
|
n := 8
|
||||||
|
if n > bInConn.Buffered() {
|
||||||
|
n = bInConn.Buffered()
|
||||||
|
}
|
||||||
|
h, err := bInConn.Peek(n)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("peek error %s ", err)
|
||||||
|
(*inConn).Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*inConn = bInConn
|
||||||
|
address := ""
|
||||||
|
var auth socks.Auth
|
||||||
|
var forwardBytes []byte
|
||||||
|
//fmt.Printf("%v", header)
|
||||||
|
if utils.IsSocks5(h) {
|
||||||
|
if *s.cfg.DisableSocks5 {
|
||||||
|
(*inConn).Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//socks5 server
|
||||||
|
var serverConn *socks.ServerConn
|
||||||
|
if s.IsBasicAuth() {
|
||||||
|
serverConn = socks.NewServerConn(inConn, time.Millisecond*time.Duration(*s.cfg.Timeout), &s.basicAuth, "", nil)
|
||||||
|
} else {
|
||||||
|
serverConn = socks.NewServerConn(inConn, time.Millisecond*time.Duration(*s.cfg.Timeout), nil, "", nil)
|
||||||
|
}
|
||||||
|
if err = serverConn.Handshake(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
address = serverConn.Target()
|
||||||
|
auth = serverConn.AuthData()
|
||||||
|
} else if utils.IsHTTP(h) {
|
||||||
|
if *s.cfg.DisableHTTP {
|
||||||
|
(*inConn).Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//http
|
||||||
|
var request utils.HTTPRequest
|
||||||
|
(*inConn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
if s.IsBasicAuth() {
|
||||||
|
request, err = utils.NewHTTPRequest(inConn, 1024, true, &s.basicAuth, s.log)
|
||||||
|
} else {
|
||||||
|
request, err = utils.NewHTTPRequest(inConn, 1024, false, nil, s.log)
|
||||||
|
}
|
||||||
|
(*inConn).SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("new http request fail,ERR: %s", err)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(h) >= 7 && strings.ToLower(string(h[:7])) == "connect" {
|
||||||
|
//https
|
||||||
|
request.HTTPSReply()
|
||||||
|
//s.log.Printf("https reply: %s", request.Host)
|
||||||
|
} else {
|
||||||
|
//forwardBytes = bytes.TrimRight(request.HeadBuf,"\r\n")
|
||||||
|
forwardBytes = request.HeadBuf
|
||||||
|
}
|
||||||
|
address = request.Host
|
||||||
|
var userpass string
|
||||||
|
if s.IsBasicAuth() {
|
||||||
|
userpass, err = request.GetAuthDataStr()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userpassA := strings.Split(userpass, ":")
|
||||||
|
if len(userpassA) == 2 {
|
||||||
|
auth = socks.Auth{User: userpassA[0], Password: userpassA[1]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil || address == "" {
|
||||||
|
s.log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(h))
|
||||||
|
(*inConn).Close()
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
err = errors.New("unknown request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//connect to parent
|
||||||
|
var outConn net.Conn
|
||||||
|
outConn, err = s.outPool.Get()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentCompress {
|
||||||
|
outConn = utils.NewCompConn(outConn)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentKey != "" {
|
||||||
|
outConn = conncrypt.New(outConn, &conncrypt.Config{
|
||||||
|
Password: *s.cfg.ParentKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.cfg.ParentAuth != "" || s.IsBasicAuth() {
|
||||||
|
forwardBytes = utils.RemoveProxyHeaders(forwardBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ask parent for connect to target address
|
||||||
|
if *s.cfg.ParentServiceType == "http" {
|
||||||
|
//http parent
|
||||||
|
isHTTPS := false
|
||||||
|
|
||||||
|
pb := new(bytes.Buffer)
|
||||||
|
if len(forwardBytes) == 0 {
|
||||||
|
isHTTPS = true
|
||||||
|
pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1\r\n", address)))
|
||||||
|
}
|
||||||
|
pb.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
||||||
|
|
||||||
|
u := ""
|
||||||
|
if *s.cfg.ParentAuth != "" {
|
||||||
|
a := strings.Split(*s.cfg.ParentAuth, ":")
|
||||||
|
if len(a) != 2 {
|
||||||
|
err = fmt.Errorf("parent auth data format error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u = fmt.Sprintf("%s:%s", a[0], a[1])
|
||||||
|
} else {
|
||||||
|
if !s.IsBasicAuth() && auth.Password != "" && auth.User != "" {
|
||||||
|
u = fmt.Sprintf("%s:%s", auth.User, auth.Password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u != "" {
|
||||||
|
pb.Write([]byte(fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
if isHTTPS {
|
||||||
|
pb.Write([]byte("\r\n"))
|
||||||
|
} else {
|
||||||
|
forwardBytes = utils.InsertProxyHeaders(forwardBytes, string(pb.Bytes()))
|
||||||
|
pb.Reset()
|
||||||
|
pb.Write(forwardBytes)
|
||||||
|
forwardBytes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = outConn.Write(pb.Bytes())
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("write CONNECT to %s , err:%s", *s.cfg.Parent, err)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isHTTPS {
|
||||||
|
reply := make([]byte, 1024)
|
||||||
|
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
|
_, err = outConn.Read(reply)
|
||||||
|
outConn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//s.log.Printf("reply: %s", string(reply[:n]))
|
||||||
|
}
|
||||||
|
} else if *s.cfg.ParentServiceType == "socks" {
|
||||||
|
s.log.Printf("connect %s", address)
|
||||||
|
//socks client
|
||||||
|
var clientConn *socks.ClientConn
|
||||||
|
if *s.cfg.ParentAuth != "" {
|
||||||
|
a := strings.Split(*s.cfg.ParentAuth, ":")
|
||||||
|
if len(a) != 2 {
|
||||||
|
err = fmt.Errorf("parent auth data format error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), &socks.Auth{User: a[0], Password: a[1]}, nil)
|
||||||
|
} else {
|
||||||
|
if !s.IsBasicAuth() && auth.Password != "" && auth.User != "" {
|
||||||
|
clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), &auth, nil)
|
||||||
|
} else {
|
||||||
|
clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = clientConn.Handshake(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//forward client data to target,if necessary.
|
||||||
|
if len(forwardBytes) > 0 {
|
||||||
|
outConn.Write(forwardBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
//bind
|
||||||
|
inAddr := (*inConn).RemoteAddr().String()
|
||||||
|
outAddr := outConn.RemoteAddr().String()
|
||||||
|
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
||||||
|
s.log.Printf("conn %s - %s released [%s]", inAddr, outAddr, address)
|
||||||
|
s.userConns.Remove(inAddr)
|
||||||
|
}, s.log)
|
||||||
|
s.log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, address)
|
||||||
|
if c, ok := s.userConns.Get(inAddr); ok {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.userConns.Set(inAddr, inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SPS) InitBasicAuth() (err error) {
|
||||||
|
if *s.cfg.DNSAddress != "" {
|
||||||
|
s.basicAuth = utils.NewBasicAuth(&(*s).domainResolver, s.log)
|
||||||
|
} else {
|
||||||
|
s.basicAuth = utils.NewBasicAuth(nil, s.log)
|
||||||
|
}
|
||||||
|
if *s.cfg.AuthURL != "" {
|
||||||
|
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||||
|
s.log.Printf("auth from %s", *s.cfg.AuthURL)
|
||||||
|
}
|
||||||
|
if *s.cfg.AuthFile != "" {
|
||||||
|
var n = 0
|
||||||
|
n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("auth-file ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total())
|
||||||
|
}
|
||||||
|
if len(*s.cfg.Auth) > 0 {
|
||||||
|
n := s.basicAuth.Add(*s.cfg.Auth)
|
||||||
|
s.log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SPS) IsBasicAuth() bool {
|
||||||
|
return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != ""
|
||||||
|
}
|
||||||
|
func (s *SPS) buildRequest(address string) (buf []byte, err error) {
|
||||||
|
host, portStr, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
err = errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 0x05, 0x01, 0 /* reserved */)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, 0x01)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 0x04)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
err = errors.New("proxy: destination host name too long: " + host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf = append(buf, 0x03)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *SPS) Resolve(address string) string {
|
||||||
|
if *s.cfg.DNSAddress == "" {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
ip, err := s.domainResolver.Resolve(address)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("dns error %s , ERR:%s", address, err)
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
130
services/tcp.go
@ -4,66 +4,100 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TCP struct {
|
type TCP struct {
|
||||||
outPool utils.OutPool
|
outPool utils.OutConn
|
||||||
cfg TCPArgs
|
cfg TCPArgs
|
||||||
|
sc *utils.ServerChannel
|
||||||
|
isStop bool
|
||||||
|
userConns utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCP() Service {
|
func NewTCP() Service {
|
||||||
return &TCP{
|
return &TCP{
|
||||||
outPool: utils.OutPool{},
|
outPool: utils.OutConn{},
|
||||||
cfg: TCPArgs{},
|
cfg: TCPArgs{},
|
||||||
|
isStop: false,
|
||||||
|
userConns: utils.NewConcurrentMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *TCP) CheckArgs() {
|
func (s *TCP) CheckArgs() (err error) {
|
||||||
if *s.cfg.Parent == "" {
|
if *s.cfg.Parent == "" {
|
||||||
log.Fatalf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local)
|
err = fmt.Errorf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "" {
|
if *s.cfg.ParentType == "" {
|
||||||
log.Fatalf("parent type unkown,use -T <tls|tcp|kcp|udp>")
|
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|kcp|udp>")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS {
|
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS {
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *TCP) InitService() {
|
func (s *TCP) InitService() (err error) {
|
||||||
s.InitOutConnPool()
|
s.InitOutConnPool()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *TCP) StopService() {
|
func (s *TCP) StopService() {
|
||||||
if s.outPool.Pool != nil {
|
defer func() {
|
||||||
s.outPool.Pool.ReleaseAll()
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop tcp service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service tcp stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
if s.sc.Listener != nil && *s.sc.Listener != nil {
|
||||||
|
(*s.sc.Listener).Close()
|
||||||
|
}
|
||||||
|
if s.sc.UDPListener != nil {
|
||||||
|
(*s.sc.UDPListener).Close()
|
||||||
|
}
|
||||||
|
for _, c := range s.userConns.Items() {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *TCP) Start(args interface{}) (err error) {
|
func (s *TCP) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(TCPArgs)
|
s.cfg = args.(TCPArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
return
|
||||||
s.InitService()
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
p, _ := strconv.Atoi(port)
|
p, _ := strconv.Atoi(port)
|
||||||
sc := utils.NewServerChannel(host, p)
|
sc := utils.NewServerChannel(host, p, s.log)
|
||||||
|
|
||||||
if *s.cfg.LocalType == TYPE_TCP {
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
err = sc.ListenTCP(s.callback)
|
err = sc.ListenTCP(s.callback)
|
||||||
} else if *s.cfg.LocalType == TYPE_TLS {
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback)
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback)
|
||||||
} else if *s.cfg.LocalType == TYPE_KCP {
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback)
|
err = sc.ListenKCP(s.cfg.KCP, s.callback, s.log)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("%s proxy on %s", s.cfg.Protocol(), (*sc.Listener).Addr())
|
s.log.Printf("%s proxy on %s", s.cfg.Protocol(), (*sc.Listener).Addr())
|
||||||
|
s.sc = &sc
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +107,7 @@ func (s *TCP) Clean() {
|
|||||||
func (s *TCP) callback(inConn net.Conn) {
|
func (s *TCP) callback(inConn net.Conn) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("%s conn handler crashed with err : %s \nstack: %s", s.cfg.Protocol(), err, string(debug.Stack()))
|
s.log.Printf("%s conn handler crashed with err : %s \nstack: %s", s.cfg.Protocol(), err, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var err error
|
var err error
|
||||||
@ -90,19 +124,15 @@ func (s *TCP) callback(inConn net.Conn) {
|
|||||||
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
|
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err)
|
s.log.Printf("connect to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *TCP) OutToTCP(inConn *net.Conn) (err error) {
|
func (s *TCP) OutToTCP(inConn *net.Conn) (err error) {
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
var _outConn interface{}
|
outConn, err = s.outPool.Get()
|
||||||
_outConn, err = s.outPool.Pool.Get()
|
|
||||||
if err == nil {
|
|
||||||
outConn = _outConn.(net.Conn)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
s.log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -111,55 +141,64 @@ func (s *TCP) OutToTCP(inConn *net.Conn) (err error) {
|
|||||||
outAddr := outConn.RemoteAddr().String()
|
outAddr := outConn.RemoteAddr().String()
|
||||||
//outLocalAddr := outConn.LocalAddr().String()
|
//outLocalAddr := outConn.LocalAddr().String()
|
||||||
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
utils.IoBind((*inConn), outConn, func(err interface{}) {
|
||||||
log.Printf("conn %s - %s released", inAddr, outAddr)
|
s.log.Printf("conn %s - %s released", inAddr, outAddr)
|
||||||
})
|
s.userConns.Remove(inAddr)
|
||||||
log.Printf("conn %s - %s connected", inAddr, outAddr)
|
}, s.log)
|
||||||
|
s.log.Printf("conn %s - %s connected", inAddr, outAddr)
|
||||||
|
if c, ok := s.userConns.Get(inAddr); ok {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.userConns.Set(inAddr, inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *TCP) OutToUDP(inConn *net.Conn) (err error) {
|
func (s *TCP) OutToUDP(inConn *net.Conn) (err error) {
|
||||||
log.Printf("conn created , remote : %s ", (*inConn).RemoteAddr())
|
s.log.Printf("conn created , remote : %s ", (*inConn).RemoteAddr())
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
(*inConn).Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
srcAddr, body, err := utils.ReadUDPPacket(bufio.NewReader(*inConn))
|
srcAddr, body, err := utils.ReadUDPPacket(bufio.NewReader(*inConn))
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
//log.Printf("connection %s released", srcAddr)
|
//s.log.Printf("connection %s released", srcAddr)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//log.Debugf("udp packet revecived:%s,%v", srcAddr, body)
|
//log.Debugf("udp packet revecived:%s,%v", srcAddr, body)
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent)
|
dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("can't resolve address: %s", err)
|
s.log.Printf("can't resolve address: %s", err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = conn.Write(body)
|
_, err = conn.Write(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//log.Debugf("send udp packet to %s success", dstAddr.String())
|
//log.Debugf("send udp packet to %s success", dstAddr.String())
|
||||||
buf := make([]byte, 512)
|
buf := make([]byte, 512)
|
||||||
len, _, err := conn.ReadFromUDP(buf)
|
len, _, err := conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
respBody := buf[0:len]
|
respBody := buf[0:len]
|
||||||
//log.Debugf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
//log.Debugf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
||||||
_, err = (*inConn).Write(utils.UDPPacket(srcAddr, respBody))
|
_, err = (*inConn).Write(utils.UDPPacket(srcAddr, respBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp response fail ,ERR:%s", err)
|
s.log.Printf("send udp response fail ,ERR:%s", err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//log.Printf("send udp response success ,from:%s", dstAddr.String())
|
//s.log.Printf("send udp response success ,from:%s", dstAddr.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -168,16 +207,13 @@ func (s *TCP) InitOutConnPool() {
|
|||||||
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP {
|
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP {
|
||||||
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
||||||
//parent string, timeout int, InitialCap int, MaxCap int
|
//parent string, timeout int, InitialCap int, MaxCap int
|
||||||
s.outPool = utils.NewOutPool(
|
s.outPool = utils.NewOutConn(
|
||||||
*s.cfg.CheckParentInterval,
|
*s.cfg.CheckParentInterval,
|
||||||
*s.cfg.ParentType,
|
*s.cfg.ParentType,
|
||||||
*s.cfg.KCPMethod,
|
s.cfg.KCP,
|
||||||
*s.cfg.KCPKey,
|
s.cfg.CertBytes, s.cfg.KeyBytes, nil,
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes,
|
|
||||||
*s.cfg.Parent,
|
*s.cfg.Parent,
|
||||||
*s.cfg.Timeout,
|
*s.cfg.Timeout,
|
||||||
*s.cfg.PoolSize,
|
|
||||||
*s.cfg.PoolSize*2,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"log"
|
"fmt"
|
||||||
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
//"github.com/xtaci/smux"
|
||||||
|
smux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerConn struct {
|
type ServerConn struct {
|
||||||
@ -17,8 +23,8 @@ type TunnelBridge struct {
|
|||||||
cfg TunnelBridgeArgs
|
cfg TunnelBridgeArgs
|
||||||
serverConns utils.ConcurrentMap
|
serverConns utils.ConcurrentMap
|
||||||
clientControlConns utils.ConcurrentMap
|
clientControlConns utils.ConcurrentMap
|
||||||
cmServer utils.ConnManager
|
isStop bool
|
||||||
cmClient utils.ConnManager
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTunnelBridge() Service {
|
func NewTunnelBridge() Service {
|
||||||
@ -26,219 +32,168 @@ func NewTunnelBridge() Service {
|
|||||||
cfg: TunnelBridgeArgs{},
|
cfg: TunnelBridgeArgs{},
|
||||||
serverConns: utils.NewConcurrentMap(),
|
serverConns: utils.NewConcurrentMap(),
|
||||||
clientControlConns: utils.NewConcurrentMap(),
|
clientControlConns: utils.NewConcurrentMap(),
|
||||||
cmServer: utils.NewConnManager(),
|
isStop: false,
|
||||||
cmClient: utils.NewConnManager(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TunnelBridge) InitService() {
|
func (s *TunnelBridge) InitService() (err error) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *TunnelBridge) CheckArgs() {
|
func (s *TunnelBridge) CheckArgs() (err error) {
|
||||||
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
log.Fatalf("cert and key file required")
|
err = fmt.Errorf("cert and key file required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *TunnelBridge) StopService() {
|
func (s *TunnelBridge) StopService() {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop tbridge service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service tbridge stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
for _, sess := range s.clientControlConns.Items() {
|
||||||
|
(*sess.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
for _, sess := range s.serverConns.Items() {
|
||||||
|
(*sess.(ServerConn).Conn).Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func (s *TunnelBridge) Start(args interface{}) (err error) {
|
func (s *TunnelBridge) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(TunnelBridgeArgs)
|
s.cfg = args.(TunnelBridgeArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
s.InitService()
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
p, _ := strconv.Atoi(port)
|
p, _ := strconv.Atoi(port)
|
||||||
sc := utils.NewServerChannel(host, p)
|
sc := utils.NewServerChannel(host, p, s.log)
|
||||||
|
|
||||||
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, func(inConn net.Conn) {
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback)
|
||||||
//log.Printf("connection from %s ", inConn.RemoteAddr())
|
|
||||||
|
|
||||||
reader := bufio.NewReader(inConn)
|
|
||||||
var err error
|
|
||||||
var connType uint8
|
|
||||||
err = utils.ReadPacket(reader, &connType)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("read error,ERR:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch connType {
|
|
||||||
case CONN_SERVER:
|
|
||||||
var key, ID, clientLocalAddr, serverID string
|
|
||||||
err = utils.ReadPacketData(reader, &key, &ID, &clientLocalAddr, &serverID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("read error,ERR:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
packet := utils.BuildPacketData(ID, clientLocalAddr, serverID)
|
|
||||||
log.Printf("server connection, key: %s , id: %s %s %s", key, ID, clientLocalAddr, serverID)
|
|
||||||
|
|
||||||
//addr := clientLocalAddr + "@" + ID
|
|
||||||
s.serverConns.Set(ID, ServerConn{
|
|
||||||
Conn: &inConn,
|
|
||||||
})
|
|
||||||
for {
|
|
||||||
item, ok := s.clientControlConns.Get(key)
|
|
||||||
if !ok {
|
|
||||||
log.Printf("client %s control conn not exists", key)
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
(*item.(*net.Conn)).SetWriteDeadline(time.Now().Add(time.Second * 3))
|
|
||||||
_, err := (*item.(*net.Conn)).Write(packet)
|
|
||||||
(*item.(*net.Conn)).SetWriteDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("%s client control conn write signal fail, err: %s, retrying...", key, err)
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
s.cmServer.Add(serverID, ID, &inConn)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case CONN_CLIENT:
|
|
||||||
var key, ID, serverID string
|
|
||||||
err = utils.ReadPacketData(reader, &key, &ID, &serverID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("read error,ERR:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("client connection , key: %s , id: %s, server id:%s", key, ID, serverID)
|
|
||||||
|
|
||||||
serverConnItem, ok := s.serverConns.Get(ID)
|
|
||||||
if !ok {
|
|
||||||
inConn.Close()
|
|
||||||
log.Printf("server conn %s exists", ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverConn := serverConnItem.(ServerConn).Conn
|
|
||||||
utils.IoBind(*serverConn, inConn, func(err interface{}) {
|
|
||||||
s.serverConns.Remove(ID)
|
|
||||||
s.cmClient.RemoveOne(key, ID)
|
|
||||||
s.cmServer.RemoveOne(serverID, ID)
|
|
||||||
log.Printf("conn %s released", ID)
|
|
||||||
})
|
|
||||||
s.cmClient.Add(key, ID, &inConn)
|
|
||||||
log.Printf("conn %s created", ID)
|
|
||||||
|
|
||||||
case CONN_CLIENT_CONTROL:
|
|
||||||
var key string
|
|
||||||
err = utils.ReadPacketData(reader, &key)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("read error,ERR:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("client control connection, key: %s", key)
|
|
||||||
if s.clientControlConns.Has(key) {
|
|
||||||
item, _ := s.clientControlConns.Get(key)
|
|
||||||
(*item.(*net.Conn)).Close()
|
|
||||||
}
|
|
||||||
s.clientControlConns.Set(key, &inConn)
|
|
||||||
log.Printf("set client %s control conn", key)
|
|
||||||
|
|
||||||
case CONN_SERVER_HEARBEAT:
|
|
||||||
var serverID string
|
|
||||||
err = utils.ReadPacketData(reader, &serverID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("read error,ERR:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("server heartbeat connection, id: %s", serverID)
|
|
||||||
writeDie := make(chan bool)
|
|
||||||
readDie := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
inConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
|
||||||
_, err = inConn.Write([]byte{0x00})
|
|
||||||
inConn.SetWriteDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("server heartbeat connection write err %s", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
}
|
|
||||||
close(writeDie)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
signal := make([]byte, 1)
|
|
||||||
inConn.SetReadDeadline(time.Now().Add(time.Second * 6))
|
|
||||||
_, err := inConn.Read(signal)
|
|
||||||
inConn.SetReadDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("server heartbeat connection read err: %s", err)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
// log.Printf("heartbeat from server ,id:%s", serverID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(readDie)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-readDie:
|
|
||||||
case <-writeDie:
|
|
||||||
}
|
|
||||||
utils.CloseConn(&inConn)
|
|
||||||
s.cmServer.Remove(serverID)
|
|
||||||
log.Printf("server heartbeat conn %s released", serverID)
|
|
||||||
case CONN_CLIENT_HEARBEAT:
|
|
||||||
var clientID string
|
|
||||||
err = utils.ReadPacketData(reader, &clientID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("read error,ERR:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("client heartbeat connection, id: %s", clientID)
|
|
||||||
writeDie := make(chan bool)
|
|
||||||
readDie := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
inConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
|
||||||
_, err = inConn.Write([]byte{0x00})
|
|
||||||
inConn.SetWriteDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("client heartbeat connection write err %s", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
}
|
|
||||||
close(writeDie)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
signal := make([]byte, 1)
|
|
||||||
inConn.SetReadDeadline(time.Now().Add(time.Second * 6))
|
|
||||||
_, err := inConn.Read(signal)
|
|
||||||
inConn.SetReadDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("client control connection read err: %s", err)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
// log.Printf("heartbeat from client ,id:%s", clientID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(readDie)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-readDie:
|
|
||||||
case <-writeDie:
|
|
||||||
}
|
|
||||||
utils.CloseConn(&inConn)
|
|
||||||
s.cmClient.Remove(clientID)
|
|
||||||
if s.clientControlConns.Has(clientID) {
|
|
||||||
item, _ := s.clientControlConns.Get(clientID)
|
|
||||||
(*item.(*net.Conn)).Close()
|
|
||||||
}
|
|
||||||
s.clientControlConns.Remove(clientID)
|
|
||||||
log.Printf("client heartbeat conn %s released", clientID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("proxy on tunnel bridge mode %s", (*sc.Listener).Addr())
|
s.log.Printf("proxy on tunnel bridge mode %s", (*sc.Listener).Addr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *TunnelBridge) Clean() {
|
func (s *TunnelBridge) Clean() {
|
||||||
s.StopService()
|
s.StopService()
|
||||||
}
|
}
|
||||||
|
func (s *TunnelBridge) callback(inConn net.Conn) {
|
||||||
|
var err error
|
||||||
|
//s.log.Printf("connection from %s ", inConn.RemoteAddr())
|
||||||
|
sess, err := smux.Server(inConn, &smux.Config{
|
||||||
|
AcceptBacklog: 256,
|
||||||
|
EnableKeepAlive: true,
|
||||||
|
KeepAliveInterval: 9 * time.Second,
|
||||||
|
ConnectionWriteTimeout: 3 * time.Second,
|
||||||
|
MaxStreamWindowSize: 512 * 1024,
|
||||||
|
LogOutput: os.Stderr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("new mux server conn error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inConn, err = sess.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("mux server conn accept error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = make([]byte, 1024)
|
||||||
|
n, _ := inConn.Read(buf)
|
||||||
|
reader := bytes.NewReader(buf[:n])
|
||||||
|
//reader := bufio.NewReader(inConn)
|
||||||
|
|
||||||
|
var connType uint8
|
||||||
|
err = utils.ReadPacket(reader, &connType)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch connType {
|
||||||
|
case CONN_SERVER:
|
||||||
|
var key, ID, clientLocalAddr, serverID string
|
||||||
|
err = utils.ReadPacketData(reader, &key, &ID, &clientLocalAddr, &serverID)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet := utils.BuildPacketData(ID, clientLocalAddr, serverID)
|
||||||
|
s.log.Printf("server connection, key: %s , id: %s %s %s", key, ID, clientLocalAddr, serverID)
|
||||||
|
|
||||||
|
//addr := clientLocalAddr + "@" + ID
|
||||||
|
s.serverConns.Set(ID, ServerConn{
|
||||||
|
Conn: &inConn,
|
||||||
|
})
|
||||||
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item, ok := s.clientControlConns.Get(key)
|
||||||
|
if !ok {
|
||||||
|
s.log.Printf("client %s control conn not exists", key)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
(*item.(*net.Conn)).SetWriteDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
_, err := (*item.(*net.Conn)).Write(packet)
|
||||||
|
(*item.(*net.Conn)).SetWriteDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("%s client control conn write signal fail, err: %s, retrying...", key, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// s.cmServer.Add(serverID, ID, &inConn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case CONN_CLIENT:
|
||||||
|
var key, ID, serverID string
|
||||||
|
err = utils.ReadPacketData(reader, &key, &ID, &serverID)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("client connection , key: %s , id: %s, server id:%s", key, ID, serverID)
|
||||||
|
|
||||||
|
serverConnItem, ok := s.serverConns.Get(ID)
|
||||||
|
if !ok {
|
||||||
|
inConn.Close()
|
||||||
|
s.log.Printf("server conn %s exists", ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverConn := serverConnItem.(ServerConn).Conn
|
||||||
|
utils.IoBind(*serverConn, inConn, func(err interface{}) {
|
||||||
|
s.serverConns.Remove(ID)
|
||||||
|
// s.cmClient.RemoveOne(key, ID)
|
||||||
|
// s.cmServer.RemoveOne(serverID, ID)
|
||||||
|
s.log.Printf("conn %s released", ID)
|
||||||
|
}, s.log)
|
||||||
|
// s.cmClient.Add(key, ID, &inConn)
|
||||||
|
s.log.Printf("conn %s created", ID)
|
||||||
|
|
||||||
|
case CONN_CLIENT_CONTROL:
|
||||||
|
var key string
|
||||||
|
err = utils.ReadPacketData(reader, &key)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("client control connection, key: %s", key)
|
||||||
|
if s.clientControlConns.Has(key) {
|
||||||
|
item, _ := s.clientControlConns.Get(key)
|
||||||
|
(*item.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.clientControlConns.Set(key, &inConn)
|
||||||
|
s.log.Printf("set client %s control conn", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -4,116 +4,90 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
//"github.com/xtaci/smux"
|
||||||
|
smux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TunnelClient struct {
|
type TunnelClient struct {
|
||||||
cfg TunnelClientArgs
|
cfg TunnelClientArgs
|
||||||
cm utils.ConnManager
|
ctrlConn net.Conn
|
||||||
ctrlConn net.Conn
|
isStop bool
|
||||||
|
userConns utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTunnelClient() Service {
|
func NewTunnelClient() Service {
|
||||||
return &TunnelClient{
|
return &TunnelClient{
|
||||||
cfg: TunnelClientArgs{},
|
cfg: TunnelClientArgs{},
|
||||||
cm: utils.NewConnManager(),
|
userConns: utils.NewConcurrentMap(),
|
||||||
|
isStop: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TunnelClient) InitService() {
|
func (s *TunnelClient) InitService() (err error) {
|
||||||
s.InitHeartbeatDeamon()
|
return
|
||||||
}
|
}
|
||||||
func (s *TunnelClient) InitHeartbeatDeamon() {
|
|
||||||
log.Printf("heartbeat started")
|
|
||||||
go func() {
|
|
||||||
var heartbeatConn net.Conn
|
|
||||||
var ID = *s.cfg.Key
|
|
||||||
for {
|
|
||||||
|
|
||||||
//close all connection
|
func (s *TunnelClient) CheckArgs() (err error) {
|
||||||
s.cm.RemoveAll()
|
|
||||||
if s.ctrlConn != nil {
|
|
||||||
s.ctrlConn.Close()
|
|
||||||
}
|
|
||||||
utils.CloseConn(&heartbeatConn)
|
|
||||||
heartbeatConn, err := s.GetInConn(CONN_CLIENT_HEARBEAT, ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("heartbeat connection err: %s, retrying...", err)
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
utils.CloseConn(&heartbeatConn)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Printf("heartbeat connection created,id:%s", ID)
|
|
||||||
writeDie := make(chan bool)
|
|
||||||
readDie := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
heartbeatConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
|
||||||
_, err = heartbeatConn.Write([]byte{0x00})
|
|
||||||
heartbeatConn.SetWriteDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("heartbeat connection write err %s", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
}
|
|
||||||
close(writeDie)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
signal := make([]byte, 1)
|
|
||||||
heartbeatConn.SetReadDeadline(time.Now().Add(time.Second * 6))
|
|
||||||
_, err := heartbeatConn.Read(signal)
|
|
||||||
heartbeatConn.SetReadDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("heartbeat connection read err: %s", err)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
//log.Printf("heartbeat from bridge")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(readDie)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-readDie:
|
|
||||||
case <-writeDie:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
func (s *TunnelClient) CheckArgs() {
|
|
||||||
if *s.cfg.Parent != "" {
|
if *s.cfg.Parent != "" {
|
||||||
log.Printf("use tls parent %s", *s.cfg.Parent)
|
s.log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("parent required")
|
err = fmt.Errorf("parent required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
log.Fatalf("cert and key file required")
|
err = fmt.Errorf("cert and key file required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *TunnelClient) StopService() {
|
func (s *TunnelClient) StopService() {
|
||||||
s.cm.RemoveAll()
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop tclient service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service tclient stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
if s.ctrlConn != nil {
|
||||||
|
s.ctrlConn.Close()
|
||||||
|
}
|
||||||
|
for _, c := range s.userConns.Items() {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func (s *TunnelClient) Start(args interface{}) (err error) {
|
func (s *TunnelClient) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(TunnelClientArgs)
|
s.cfg = args.(TunnelClientArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
s.InitService()
|
return
|
||||||
log.Printf("proxy on tunnel client mode")
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.log.Printf("proxy on tunnel client mode")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
//close all conn
|
if s.isStop {
|
||||||
s.cm.Remove(*s.cfg.Key)
|
return
|
||||||
|
}
|
||||||
if s.ctrlConn != nil {
|
if s.ctrlConn != nil {
|
||||||
s.ctrlConn.Close()
|
s.ctrlConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ctrlConn, err = s.GetInConn(CONN_CLIENT_CONTROL, *s.cfg.Key)
|
s.ctrlConn, err = s.GetInConn(CONN_CLIENT_CONTROL, *s.cfg.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("control connection err: %s, retrying...", err)
|
s.log.Printf("control connection err: %s, retrying...", err)
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
if s.ctrlConn != nil {
|
if s.ctrlConn != nil {
|
||||||
s.ctrlConn.Close()
|
s.ctrlConn.Close()
|
||||||
@ -121,16 +95,19 @@ func (s *TunnelClient) Start(args interface{}) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
var ID, clientLocalAddr, serverID string
|
var ID, clientLocalAddr, serverID string
|
||||||
err = utils.ReadPacketData(s.ctrlConn, &ID, &clientLocalAddr, &serverID)
|
err = utils.ReadPacketData(s.ctrlConn, &ID, &clientLocalAddr, &serverID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s.ctrlConn != nil {
|
if s.ctrlConn != nil {
|
||||||
s.ctrlConn.Close()
|
s.ctrlConn.Close()
|
||||||
}
|
}
|
||||||
log.Printf("read connection signal err: %s, retrying...", err)
|
s.log.Printf("read connection signal err: %s, retrying...", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
log.Printf("signal revecived:%s %s %s", serverID, ID, clientLocalAddr)
|
s.log.Printf("signal revecived:%s %s %s", serverID, ID, clientLocalAddr)
|
||||||
protocol := clientLocalAddr[:3]
|
protocol := clientLocalAddr[:3]
|
||||||
localAddr := clientLocalAddr[4:]
|
localAddr := clientLocalAddr[4:]
|
||||||
if protocol == "udp" {
|
if protocol == "udp" {
|
||||||
@ -160,9 +137,28 @@ func (s *TunnelClient) GetInConn(typ uint8, data ...string) (outConn net.Conn, e
|
|||||||
}
|
}
|
||||||
func (s *TunnelClient) GetConn() (conn net.Conn, err error) {
|
func (s *TunnelClient) GetConn() (conn net.Conn, err error) {
|
||||||
var _conn tls.Conn
|
var _conn tls.Conn
|
||||||
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conn = net.Conn(&_conn)
|
conn = net.Conn(&_conn)
|
||||||
|
c, e := smux.Client(conn, &smux.Config{
|
||||||
|
AcceptBacklog: 256,
|
||||||
|
EnableKeepAlive: true,
|
||||||
|
KeepAliveInterval: 9 * time.Second,
|
||||||
|
ConnectionWriteTimeout: 3 * time.Second,
|
||||||
|
MaxStreamWindowSize: 512 * 1024,
|
||||||
|
LogOutput: os.Stderr,
|
||||||
|
})
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("new mux client conn error,ERR:%s", e)
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, e = c.OpenStream()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("mux client conn open stream error,ERR:%s", e)
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -171,30 +167,39 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) {
|
|||||||
var err error
|
var err error
|
||||||
// for {
|
// for {
|
||||||
for {
|
for {
|
||||||
s.cm.RemoveOne(*s.cfg.Key, ID)
|
if s.isStop {
|
||||||
|
if inConn != nil {
|
||||||
|
inConn.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// s.cm.RemoveOne(*s.cfg.Key, ID)
|
||||||
inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID)
|
inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
log.Printf("connection err: %s, retrying...", err)
|
s.log.Printf("connection err: %s, retrying...", err)
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.cm.Add(*s.cfg.Key, ID, &inConn)
|
// s.cm.Add(*s.cfg.Key, ID, &inConn)
|
||||||
log.Printf("conn %s created", ID)
|
s.log.Printf("conn %s created", ID)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
srcAddr, body, err := utils.ReadUDPPacket(inConn)
|
srcAddr, body, err := utils.ReadUDPPacket(inConn)
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
log.Printf("connection %s released", ID)
|
s.log.Printf("connection %s released", ID)
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Printf("udp packet revecived fail, err: %s", err)
|
s.log.Printf("udp packet revecived fail, err: %s", err)
|
||||||
} else {
|
} else {
|
||||||
//log.Printf("udp packet revecived:%s,%v", srcAddr, body)
|
//s.log.Printf("udp packet revecived:%s,%v", srcAddr, body)
|
||||||
go s.processUDPPacket(&inConn, srcAddr, localAddr, body)
|
go s.processUDPPacket(&inConn, srcAddr, localAddr, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,48 +209,51 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) {
|
|||||||
func (s *TunnelClient) processUDPPacket(inConn *net.Conn, srcAddr, localAddr string, body []byte) {
|
func (s *TunnelClient) processUDPPacket(inConn *net.Conn, srcAddr, localAddr string, body []byte) {
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp", localAddr)
|
dstAddr, err := net.ResolveUDPAddr("udp", localAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("can't resolve address: %s", err)
|
s.log.Printf("can't resolve address: %s", err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = conn.Write(body)
|
_, err = conn.Write(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("send udp packet to %s success", dstAddr.String())
|
//s.log.Printf("send udp packet to %s success", dstAddr.String())
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
length, _, err := conn.ReadFromUDP(buf)
|
length, _, err := conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respBody := buf[0:length]
|
respBody := buf[0:length]
|
||||||
//log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
//s.log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
||||||
bs := utils.UDPPacket(srcAddr, respBody)
|
bs := utils.UDPPacket(srcAddr, respBody)
|
||||||
_, err = (*inConn).Write(bs)
|
_, err = (*inConn).Write(bs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp response fail ,ERR:%s", err)
|
s.log.Printf("send udp response fail ,ERR:%s", err)
|
||||||
utils.CloseConn(inConn)
|
utils.CloseConn(inConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("send udp response success ,from:%s ,%d ,%v", dstAddr.String(), len(bs), bs)
|
//s.log.Printf("send udp response success ,from:%s ,%d ,%v", dstAddr.String(), len(bs), bs)
|
||||||
}
|
}
|
||||||
func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) {
|
func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) {
|
||||||
var inConn, outConn net.Conn
|
var inConn, outConn net.Conn
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID)
|
inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
log.Printf("connection err: %s, retrying...", err)
|
s.log.Printf("connection err: %s, retrying...", err)
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
@ -255,13 +263,16 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) {
|
|||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
i++
|
i++
|
||||||
outConn, err = utils.ConnectHost(localAddr, *s.cfg.Timeout)
|
outConn, err = utils.ConnectHost(localAddr, *s.cfg.Timeout)
|
||||||
if err == nil || i == 3 {
|
if err == nil || i == 3 {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
if i == 3 {
|
if i == 3 {
|
||||||
log.Printf("connect to %s err: %s, retrying...", localAddr, err)
|
s.log.Printf("connect to %s err: %s, retrying...", localAddr, err)
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -270,13 +281,17 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.CloseConn(&inConn)
|
utils.CloseConn(&inConn)
|
||||||
utils.CloseConn(&outConn)
|
utils.CloseConn(&outConn)
|
||||||
log.Printf("build connection error, err: %s", err)
|
s.log.Printf("build connection error, err: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
inAddr := inConn.RemoteAddr().String()
|
||||||
utils.IoBind(inConn, outConn, func(err interface{}) {
|
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||||
log.Printf("conn %s released", ID)
|
s.log.Printf("conn %s released", ID)
|
||||||
s.cm.RemoveOne(*s.cfg.Key, ID)
|
s.userConns.Remove(inAddr)
|
||||||
})
|
}, s.log)
|
||||||
s.cm.Add(*s.cfg.Key, ID, &inConn)
|
if c, ok := s.userConns.Get(inAddr); ok {
|
||||||
log.Printf("conn %s created", ID)
|
(*c.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.userConns.Set(inAddr, &inConn)
|
||||||
|
s.log.Printf("conn %s created", ID)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,27 +4,36 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
|
||||||
|
//"github.com/xtaci/smux"
|
||||||
|
smux "github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TunnelServer struct {
|
type TunnelServer struct {
|
||||||
cfg TunnelServerArgs
|
cfg TunnelServerArgs
|
||||||
udpChn chan UDPItem
|
udpChn chan UDPItem
|
||||||
sc utils.ServerChannel
|
sc utils.ServerChannel
|
||||||
|
isStop bool
|
||||||
|
udpConn *net.Conn
|
||||||
|
userConns utils.ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunnelServerManager struct {
|
type TunnelServerManager struct {
|
||||||
cfg TunnelServerArgs
|
cfg TunnelServerArgs
|
||||||
udpChn chan UDPItem
|
udpChn chan UDPItem
|
||||||
sc utils.ServerChannel
|
|
||||||
serverID string
|
serverID string
|
||||||
cm utils.ConnManager
|
servers []*Service
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTunnelServerManager() Service {
|
func NewTunnelServerManager() Service {
|
||||||
@ -32,22 +41,28 @@ func NewTunnelServerManager() Service {
|
|||||||
cfg: TunnelServerArgs{},
|
cfg: TunnelServerArgs{},
|
||||||
udpChn: make(chan UDPItem, 50000),
|
udpChn: make(chan UDPItem, 50000),
|
||||||
serverID: utils.Uniqueid(),
|
serverID: utils.Uniqueid(),
|
||||||
cm: utils.NewConnManager(),
|
servers: []*Service{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *TunnelServerManager) Start(args interface{}) (err error) {
|
func (s *TunnelServerManager) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(TunnelServerArgs)
|
s.cfg = args.(TunnelServerArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if *s.cfg.Parent != "" {
|
if *s.cfg.Parent != "" {
|
||||||
log.Printf("use tls parent %s", *s.cfg.Parent)
|
s.log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("parent required")
|
err = fmt.Errorf("parent required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.InitService()
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("server id: %s", s.serverID)
|
s.log.Printf("server id: %s", s.serverID)
|
||||||
//log.Printf("route:%v", *s.cfg.Route)
|
//s.log.Printf("route:%v", *s.cfg.Route)
|
||||||
for _, _info := range *s.cfg.Route {
|
for _, _info := range *s.cfg.Route {
|
||||||
IsUDP := *s.cfg.IsUDP
|
IsUDP := *s.cfg.IsUDP
|
||||||
if strings.HasPrefix(_info, "udp://") {
|
if strings.HasPrefix(_info, "udp://") {
|
||||||
@ -79,11 +94,12 @@ func (s *TunnelServerManager) Start(args interface{}) (err error) {
|
|||||||
Key: &KEY,
|
Key: &KEY,
|
||||||
Timeout: s.cfg.Timeout,
|
Timeout: s.cfg.Timeout,
|
||||||
Mgr: s,
|
Mgr: s,
|
||||||
})
|
}, log)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.servers = append(s.servers, &server)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -91,81 +107,32 @@ func (s *TunnelServerManager) Clean() {
|
|||||||
s.StopService()
|
s.StopService()
|
||||||
}
|
}
|
||||||
func (s *TunnelServerManager) StopService() {
|
func (s *TunnelServerManager) StopService() {
|
||||||
s.cm.RemoveAll()
|
for _, server := range s.servers {
|
||||||
}
|
(*server).Clean()
|
||||||
func (s *TunnelServerManager) CheckArgs() {
|
|
||||||
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
|
||||||
log.Fatalf("cert and key file required")
|
|
||||||
}
|
}
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
|
||||||
}
|
}
|
||||||
func (s *TunnelServerManager) InitService() {
|
func (s *TunnelServerManager) CheckArgs() (err error) {
|
||||||
s.InitHeartbeatDeamon()
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
err = fmt.Errorf("cert and key file required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *TunnelServerManager) InitHeartbeatDeamon() {
|
func (s *TunnelServerManager) InitService() (err error) {
|
||||||
log.Printf("heartbeat started")
|
return
|
||||||
go func() {
|
|
||||||
var heartbeatConn net.Conn
|
|
||||||
var ID string
|
|
||||||
for {
|
|
||||||
//close all connection
|
|
||||||
s.cm.Remove(ID)
|
|
||||||
utils.CloseConn(&heartbeatConn)
|
|
||||||
heartbeatConn, ID, err := s.GetOutConn(CONN_SERVER_HEARBEAT)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("heartbeat connection err: %s, retrying...", err)
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
utils.CloseConn(&heartbeatConn)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Printf("heartbeat connection created,id:%s", ID)
|
|
||||||
writeDie := make(chan bool)
|
|
||||||
readDie := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
heartbeatConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
|
||||||
_, err = heartbeatConn.Write([]byte{0x00})
|
|
||||||
heartbeatConn.SetWriteDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("heartbeat connection write err %s", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
}
|
|
||||||
close(writeDie)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
signal := make([]byte, 1)
|
|
||||||
heartbeatConn.SetReadDeadline(time.Now().Add(time.Second * 6))
|
|
||||||
_, err := heartbeatConn.Read(signal)
|
|
||||||
heartbeatConn.SetReadDeadline(time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("heartbeat connection read err: %s", err)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
// log.Printf("heartbeat from bridge")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(readDie)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-readDie:
|
|
||||||
case <-writeDie:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TunnelServerManager) GetOutConn(typ uint8) (outConn net.Conn, ID string, err error) {
|
func (s *TunnelServerManager) GetOutConn(typ uint8) (outConn net.Conn, ID string, err error) {
|
||||||
outConn, err = s.GetConn()
|
outConn, err = s.GetConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connection err: %s", err)
|
s.log.Printf("connection err: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ID = s.serverID
|
ID = s.serverID
|
||||||
_, err = outConn.Write(utils.BuildPacket(typ, s.serverID))
|
_, err = outConn.Write(utils.BuildPacket(typ, s.serverID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("write connection data err: %s ,retrying...", err)
|
s.log.Printf("write connection data err: %s ,retrying...", err)
|
||||||
utils.CloseConn(&outConn)
|
utils.CloseConn(&outConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -173,7 +140,7 @@ func (s *TunnelServerManager) GetOutConn(typ uint8) (outConn net.Conn, ID string
|
|||||||
}
|
}
|
||||||
func (s *TunnelServerManager) GetConn() (conn net.Conn, err error) {
|
func (s *TunnelServerManager) GetConn() (conn net.Conn, err error) {
|
||||||
var _conn tls.Conn
|
var _conn tls.Conn
|
||||||
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conn = net.Conn(&_conn)
|
conn = net.Conn(&_conn)
|
||||||
}
|
}
|
||||||
@ -181,8 +148,10 @@ func (s *TunnelServerManager) GetConn() (conn net.Conn, err error) {
|
|||||||
}
|
}
|
||||||
func NewTunnelServer() Service {
|
func NewTunnelServer() Service {
|
||||||
return &TunnelServer{
|
return &TunnelServer{
|
||||||
cfg: TunnelServerArgs{},
|
cfg: TunnelServerArgs{},
|
||||||
udpChn: make(chan UDPItem, 50000),
|
udpChn: make(chan UDPItem, 50000),
|
||||||
|
isStop: false,
|
||||||
|
userConns: utils.NewConcurrentMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,22 +161,54 @@ type UDPItem struct {
|
|||||||
srcAddr *net.UDPAddr
|
srcAddr *net.UDPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TunnelServer) InitService() {
|
func (s *TunnelServer) StopService() {
|
||||||
s.UDPConnDeamon()
|
defer func() {
|
||||||
}
|
e := recover()
|
||||||
func (s *TunnelServer) CheckArgs() {
|
if e != nil {
|
||||||
if *s.cfg.Remote == "" {
|
s.log.Printf("stop server service crashed,%s", e)
|
||||||
log.Fatalf("remote required")
|
} else {
|
||||||
|
s.log.Printf("service server stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
|
||||||
|
if s.sc.Listener != nil {
|
||||||
|
(*s.sc.Listener).Close()
|
||||||
|
}
|
||||||
|
if s.sc.UDPListener != nil {
|
||||||
|
(*s.sc.UDPListener).Close()
|
||||||
|
}
|
||||||
|
if s.udpConn != nil {
|
||||||
|
(*s.udpConn).Close()
|
||||||
|
}
|
||||||
|
for _, c := range s.userConns.Items() {
|
||||||
|
(*c.(*net.Conn)).Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (s *TunnelServer) InitService() (err error) {
|
||||||
|
s.UDPConnDeamon()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *TunnelServer) CheckArgs() (err error) {
|
||||||
|
if *s.cfg.Remote == "" {
|
||||||
|
err = fmt.Errorf("remote required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TunnelServer) Start(args interface{}) (err error) {
|
func (s *TunnelServer) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(TunnelServerArgs)
|
s.cfg = args.(TunnelServerArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
s.InitService()
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
p, _ := strconv.Atoi(port)
|
p, _ := strconv.Atoi(port)
|
||||||
s.sc = utils.NewServerChannel(host, p)
|
s.sc = utils.NewServerChannel(host, p, s.log)
|
||||||
if *s.cfg.IsUDP {
|
if *s.cfg.IsUDP {
|
||||||
err = s.sc.ListenUDP(func(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
err = s.sc.ListenUDP(func(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||||
s.udpChn <- UDPItem{
|
s.udpChn <- UDPItem{
|
||||||
@ -219,39 +220,45 @@ func (s *TunnelServer) Start(args interface{}) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("proxy on udp tunnel server mode %s", (*s.sc.UDPListener).LocalAddr())
|
s.log.Printf("proxy on udp tunnel server mode %s", (*s.sc.UDPListener).LocalAddr())
|
||||||
} else {
|
} else {
|
||||||
err = s.sc.ListenTCP(func(inConn net.Conn) {
|
err = s.sc.ListenTCP(func(inConn net.Conn) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("tserver conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
s.log.Printf("tserver conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
var ID string
|
var ID string
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
outConn, ID, err = s.GetOutConn(CONN_SERVER)
|
outConn, ID, err = s.GetOutConn(CONN_SERVER)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.CloseConn(&outConn)
|
utils.CloseConn(&outConn)
|
||||||
log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
s.log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inAddr := inConn.RemoteAddr().String()
|
||||||
utils.IoBind(inConn, outConn, func(err interface{}) {
|
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||||
s.cfg.Mgr.cm.RemoveOne(s.cfg.Mgr.serverID, ID)
|
s.userConns.Remove(inAddr)
|
||||||
log.Printf("%s conn %s released", *s.cfg.Key, ID)
|
s.log.Printf("%s conn %s released", *s.cfg.Key, ID)
|
||||||
})
|
}, s.log)
|
||||||
//add conn
|
if c, ok := s.userConns.Get(inAddr); ok {
|
||||||
s.cfg.Mgr.cm.Add(s.cfg.Mgr.serverID, ID, &inConn)
|
(*c.(*net.Conn)).Close()
|
||||||
log.Printf("%s conn %s created", *s.cfg.Key, ID)
|
}
|
||||||
|
s.userConns.Set(inAddr, &inConn)
|
||||||
|
s.log.Printf("%s conn %s created", *s.cfg.Key, ID)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("proxy on tunnel server mode %s", (*s.sc.Listener).Addr())
|
s.log.Printf("proxy on tunnel server mode %s", (*s.sc.Listener).Addr())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -261,7 +268,7 @@ func (s *TunnelServer) Clean() {
|
|||||||
func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err error) {
|
func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err error) {
|
||||||
outConn, err = s.GetConn()
|
outConn, err = s.GetConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connection err: %s", err)
|
s.log.Printf("connection err: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
remoteAddr := "tcp:" + *s.cfg.Remote
|
remoteAddr := "tcp:" + *s.cfg.Remote
|
||||||
@ -271,7 +278,7 @@ func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err e
|
|||||||
ID = utils.Uniqueid()
|
ID = utils.Uniqueid()
|
||||||
_, err = outConn.Write(utils.BuildPacket(typ, *s.cfg.Key, ID, remoteAddr, s.cfg.Mgr.serverID))
|
_, err = outConn.Write(utils.BuildPacket(typ, *s.cfg.Key, ID, remoteAddr, s.cfg.Mgr.serverID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("write connection data err: %s ,retrying...", err)
|
s.log.Printf("write connection data err: %s ,retrying...", err)
|
||||||
utils.CloseConn(&outConn)
|
utils.CloseConn(&outConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -279,9 +286,28 @@ func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err e
|
|||||||
}
|
}
|
||||||
func (s *TunnelServer) GetConn() (conn net.Conn, err error) {
|
func (s *TunnelServer) GetConn() (conn net.Conn, err error) {
|
||||||
var _conn tls.Conn
|
var _conn tls.Conn
|
||||||
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conn = net.Conn(&_conn)
|
conn = net.Conn(&_conn)
|
||||||
|
c, e := smux.Client(conn, &smux.Config{
|
||||||
|
AcceptBacklog: 256,
|
||||||
|
EnableKeepAlive: true,
|
||||||
|
KeepAliveInterval: 9 * time.Second,
|
||||||
|
ConnectionWriteTimeout: 3 * time.Second,
|
||||||
|
MaxStreamWindowSize: 512 * 1024,
|
||||||
|
LogOutput: os.Stderr,
|
||||||
|
})
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("new mux client conn error,ERR:%s", e)
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, e = c.OpenStream()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("mux client conn open stream error,ERR:%s", e)
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -289,7 +315,7 @@ func (s *TunnelServer) UDPConnDeamon() {
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("udp conn deamon crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
s.log.Printf("udp conn deamon crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
@ -298,48 +324,60 @@ func (s *TunnelServer) UDPConnDeamon() {
|
|||||||
// var cmdChn = make(chan bool, 1000)
|
// var cmdChn = make(chan bool, 1000)
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
item := <-s.udpChn
|
item := <-s.udpChn
|
||||||
RETRY:
|
RETRY:
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
if outConn == nil {
|
if outConn == nil {
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
outConn, ID, err = s.GetOutConn(CONN_SERVER)
|
outConn, ID, err = s.GetOutConn(CONN_SERVER)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// cmdChn <- true
|
// cmdChn <- true
|
||||||
outConn = nil
|
outConn = nil
|
||||||
utils.CloseConn(&outConn)
|
utils.CloseConn(&outConn)
|
||||||
log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
s.log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
go func(outConn net.Conn, ID string) {
|
go func(outConn net.Conn, ID string) {
|
||||||
go func() {
|
if s.udpConn != nil {
|
||||||
// <-cmdChn
|
(*s.udpConn).Close()
|
||||||
// outConn.Close()
|
}
|
||||||
}()
|
s.udpConn = &outConn
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn)
|
srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn)
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
log.Printf("UDP deamon connection %s exited", ID)
|
s.log.Printf("UDP deamon connection %s exited", ID)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body)
|
s.log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//log.Printf("udp packet revecived over parent , local:%s", srcAddrFromConn)
|
//s.log.Printf("udp packet revecived over parent , local:%s", srcAddrFromConn)
|
||||||
_srcAddr := strings.Split(srcAddrFromConn, ":")
|
_srcAddr := strings.Split(srcAddrFromConn, ":")
|
||||||
if len(_srcAddr) != 2 {
|
if len(_srcAddr) != 2 {
|
||||||
log.Printf("parse revecived udp packet fail, addr error : %s", srcAddrFromConn)
|
s.log.Printf("parse revecived udp packet fail, addr error : %s", srcAddrFromConn)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
port, _ := strconv.Atoi(_srcAddr[1])
|
port, _ := strconv.Atoi(_srcAddr[1])
|
||||||
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
|
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
|
||||||
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
|
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("udp response to local %s fail,ERR:%s", srcAddrFromConn, err)
|
s.log.Printf("udp response to local %s fail,ERR:%s", srcAddrFromConn, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//log.Printf("udp response to local %s success , %v", srcAddrFromConn, body)
|
//s.log.Printf("udp response to local %s success , %v", srcAddrFromConn, body)
|
||||||
}
|
}
|
||||||
}(outConn, ID)
|
}(outConn, ID)
|
||||||
break
|
break
|
||||||
@ -352,10 +390,10 @@ func (s *TunnelServer) UDPConnDeamon() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.CloseConn(&outConn)
|
utils.CloseConn(&outConn)
|
||||||
outConn = nil
|
outConn = nil
|
||||||
log.Printf("write udp packet to %s fail ,flush err:%s ,retrying...", *s.cfg.Parent, err)
|
s.log.Printf("write udp packet to %s fail ,flush err:%s ,retrying...", *s.cfg.Parent, err)
|
||||||
goto RETRY
|
goto RETRY
|
||||||
}
|
}
|
||||||
//log.Printf("write packet %v", *item.packet)
|
//s.log.Printf("write packet %v", *item.packet)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
126
services/udp.go
@ -5,64 +5,92 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"proxy/utils"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UDP struct {
|
type UDP struct {
|
||||||
p utils.ConcurrentMap
|
p utils.ConcurrentMap
|
||||||
outPool utils.OutPool
|
outPool utils.OutConn
|
||||||
cfg UDPArgs
|
cfg UDPArgs
|
||||||
sc *utils.ServerChannel
|
sc *utils.ServerChannel
|
||||||
|
isStop bool
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDP() Service {
|
func NewUDP() Service {
|
||||||
return &UDP{
|
return &UDP{
|
||||||
outPool: utils.OutPool{},
|
outPool: utils.OutConn{},
|
||||||
p: utils.NewConcurrentMap(),
|
p: utils.NewConcurrentMap(),
|
||||||
|
isStop: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *UDP) CheckArgs() {
|
func (s *UDP) CheckArgs() (err error) {
|
||||||
if *s.cfg.Parent == "" {
|
if *s.cfg.Parent == "" {
|
||||||
log.Fatalf("parent required for udp %s", *s.cfg.Local)
|
err = fmt.Errorf("parent required for udp %s", *s.cfg.Local)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "" {
|
if *s.cfg.ParentType == "" {
|
||||||
log.Fatalf("parent type unkown,use -T <tls|tcp>")
|
err = fmt.Errorf("parent type unkown,use -T <tls|tcp>")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if *s.cfg.ParentType == "tls" {
|
if *s.cfg.ParentType == "tls" {
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *UDP) InitService() {
|
func (s *UDP) InitService() (err error) {
|
||||||
if *s.cfg.ParentType != TYPE_UDP {
|
if *s.cfg.ParentType != TYPE_UDP {
|
||||||
s.InitOutConnPool()
|
s.InitOutConnPool()
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
func (s *UDP) StopService() {
|
func (s *UDP) StopService() {
|
||||||
if s.outPool.Pool != nil {
|
defer func() {
|
||||||
s.outPool.Pool.ReleaseAll()
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
s.log.Printf("stop udp service crashed,%s", e)
|
||||||
|
} else {
|
||||||
|
s.log.Printf("service udp stoped")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.isStop = true
|
||||||
|
if s.sc.Listener != nil && *s.sc.Listener != nil {
|
||||||
|
(*s.sc.Listener).Close()
|
||||||
|
}
|
||||||
|
if s.sc.UDPListener != nil {
|
||||||
|
(*s.sc.UDPListener).Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *UDP) Start(args interface{}) (err error) {
|
func (s *UDP) Start(args interface{}, log *logger.Logger) (err error) {
|
||||||
|
s.log = log
|
||||||
s.cfg = args.(UDPArgs)
|
s.cfg = args.(UDPArgs)
|
||||||
s.CheckArgs()
|
if err = s.CheckArgs(); err != nil {
|
||||||
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
return
|
||||||
s.InitService()
|
}
|
||||||
|
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
if err = s.InitService(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
p, _ := strconv.Atoi(port)
|
p, _ := strconv.Atoi(port)
|
||||||
sc := utils.NewServerChannel(host, p)
|
sc := utils.NewServerChannel(host, p, s.log)
|
||||||
s.sc = &sc
|
s.sc = &sc
|
||||||
err = sc.ListenUDP(s.callback)
|
err = sc.ListenUDP(s.callback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("udp proxy on %s", (*sc.UDPListener).LocalAddr())
|
s.log.Printf("udp proxy on %s", (*sc.UDPListener).LocalAddr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +100,7 @@ func (s *UDP) Clean() {
|
|||||||
func (s *UDP) callback(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
func (s *UDP) callback(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("udp conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
s.log.Printf("udp conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var err error
|
var err error
|
||||||
@ -87,14 +115,14 @@ func (s *UDP) callback(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
|||||||
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
|
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err)
|
s.log.Printf("connect to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *UDP) GetConn(connKey string) (conn net.Conn, isNew bool, err error) {
|
func (s *UDP) GetConn(connKey string) (conn net.Conn, isNew bool, err error) {
|
||||||
isNew = !s.p.Has(connKey)
|
isNew = !s.p.Has(connKey)
|
||||||
var _conn interface{}
|
var _conn interface{}
|
||||||
if isNew {
|
if isNew {
|
||||||
_conn, err = s.outPool.Pool.Get()
|
_conn, err = s.outPool.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
@ -108,112 +136,114 @@ func (s *UDP) GetConn(connKey string) (conn net.Conn, isNew bool, err error) {
|
|||||||
func (s *UDP) OutToTCP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err error) {
|
func (s *UDP) OutToTCP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err error) {
|
||||||
numLocal := crc32.ChecksumIEEE([]byte(localAddr.String()))
|
numLocal := crc32.ChecksumIEEE([]byte(localAddr.String()))
|
||||||
numSrc := crc32.ChecksumIEEE([]byte(srcAddr.String()))
|
numSrc := crc32.ChecksumIEEE([]byte(srcAddr.String()))
|
||||||
mod := uint32(*s.cfg.PoolSize)
|
mod := uint32(10)
|
||||||
if mod == 0 {
|
if mod == 0 {
|
||||||
mod = 10
|
mod = 10
|
||||||
}
|
}
|
||||||
connKey := uint64((numLocal/10)*10 + numSrc%mod)
|
connKey := uint64((numLocal/10)*10 + numSrc%mod)
|
||||||
conn, isNew, err := s.GetConn(fmt.Sprintf("%d", connKey))
|
conn, isNew, err := s.GetConn(fmt.Sprintf("%d", connKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("upd get conn to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err)
|
s.log.Printf("upd get conn to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isNew {
|
if isNew {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("udp conn handler out to tcp crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
s.log.Printf("udp conn handler out to tcp crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Printf("conn %d created , local: %s", connKey, srcAddr.String())
|
s.log.Printf("conn %d created , local: %s", connKey, srcAddr.String())
|
||||||
for {
|
for {
|
||||||
|
if s.isStop {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
srcAddrFromConn, body, err := utils.ReadUDPPacket(bufio.NewReader(conn))
|
srcAddrFromConn, body, err := utils.ReadUDPPacket(bufio.NewReader(conn))
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
//log.Printf("connection %d released", connKey)
|
//s.log.Printf("connection %d released", connKey)
|
||||||
s.p.Remove(fmt.Sprintf("%d", connKey))
|
s.p.Remove(fmt.Sprintf("%d", connKey))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("parse revecived udp packet fail, err: %s", err)
|
s.log.Printf("parse revecived udp packet fail, err: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//log.Printf("udp packet revecived over parent , local:%s", srcAddrFromConn)
|
//s.log.Printf("udp packet revecived over parent , local:%s", srcAddrFromConn)
|
||||||
_srcAddr := strings.Split(srcAddrFromConn, ":")
|
_srcAddr := strings.Split(srcAddrFromConn, ":")
|
||||||
if len(_srcAddr) != 2 {
|
if len(_srcAddr) != 2 {
|
||||||
log.Printf("parse revecived udp packet fail, addr error : %s", srcAddrFromConn)
|
s.log.Printf("parse revecived udp packet fail, addr error : %s", srcAddrFromConn)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
port, _ := strconv.Atoi(_srcAddr[1])
|
port, _ := strconv.Atoi(_srcAddr[1])
|
||||||
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
|
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
|
||||||
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
|
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("udp response to local %s fail,ERR:%s", srcAddr, err)
|
s.log.Printf("udp response to local %s fail,ERR:%s", srcAddr, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//log.Printf("udp response to local %s success", srcAddr)
|
//s.log.Printf("udp response to local %s success", srcAddr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
//log.Printf("select conn %d , local: %s", connKey, srcAddr.String())
|
//s.log.Printf("select conn %d , local: %s", connKey, srcAddr.String())
|
||||||
writer := bufio.NewWriter(conn)
|
writer := bufio.NewWriter(conn)
|
||||||
//fmt.Println(conn, writer)
|
//fmt.Println(conn, writer)
|
||||||
writer.Write(utils.UDPPacket(srcAddr.String(), packet))
|
writer.Write(utils.UDPPacket(srcAddr.String(), packet))
|
||||||
err = writer.Flush()
|
err = writer.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("write udp packet to %s fail ,flush err:%s", *s.cfg.Parent, err)
|
s.log.Printf("write udp packet to %s fail ,flush err:%s", *s.cfg.Parent, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("write packet %v", packet)
|
//s.log.Printf("write packet %v", packet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *UDP) OutToUDP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err error) {
|
func (s *UDP) OutToUDP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err error) {
|
||||||
//log.Printf("udp packet revecived:%s,%v", srcAddr, packet)
|
//s.log.Printf("udp packet revecived:%s,%v", srcAddr, packet)
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent)
|
dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("resolve udp addr %s fail fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("resolve udp addr %s fail fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
||||||
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("connect to udp %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
|
||||||
_, err = conn.Write(packet)
|
_, err = conn.Write(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("send udp packet to %s success", dstAddr.String())
|
//s.log.Printf("send udp packet to %s success", dstAddr.String())
|
||||||
buf := make([]byte, 512)
|
buf := make([]byte, 512)
|
||||||
len, _, err := conn.ReadFromUDP(buf)
|
len, _, err := conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
s.log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
//s.log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
|
||||||
_, err = s.sc.UDPListener.WriteToUDP(buf[0:len], srcAddr)
|
_, err = s.sc.UDPListener.WriteToUDP(buf[0:len], srcAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("send udp response to cluster fail ,ERR:%s", err)
|
s.log.Printf("send udp response to cluster fail ,ERR:%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//log.Printf("send udp response to cluster success ,from:%s", dstAddr.String())
|
//s.log.Printf("send udp response to cluster success ,from:%s", dstAddr.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *UDP) InitOutConnPool() {
|
func (s *UDP) InitOutConnPool() {
|
||||||
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP {
|
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP {
|
||||||
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
//dur int, isTLS bool, certBytes, keyBytes []byte,
|
||||||
//parent string, timeout int, InitialCap int, MaxCap int
|
//parent string, timeout int, InitialCap int, MaxCap int
|
||||||
s.outPool = utils.NewOutPool(
|
s.outPool = utils.NewOutConn(
|
||||||
*s.cfg.CheckParentInterval,
|
*s.cfg.CheckParentInterval,
|
||||||
*s.cfg.ParentType,
|
*s.cfg.ParentType,
|
||||||
"", "",
|
kcpcfg.KCPConfigArgs{},
|
||||||
s.cfg.CertBytes, s.cfg.KeyBytes,
|
s.cfg.CertBytes, s.cfg.KeyBytes, nil,
|
||||||
*s.cfg.Parent,
|
*s.cfg.Parent,
|
||||||
*s.cfg.Timeout,
|
*s.cfg.Timeout,
|
||||||
*s.cfg.PoolSize,
|
|
||||||
*s.cfg.PoolSize*2,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
rm -rf /usr/bin/proxy
|
rm -rf /usr/bin/proxy
|
||||||
rm -rf /usr/bin/proxyd
|
|
||||||
echo "uninstall done"
|
echo "uninstall done"
|
||||||
|
|||||||
95
utils/conncrypt/conncrypt.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package conncrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/sha256"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Confg defaults
|
||||||
|
const DefaultIterations = 2048
|
||||||
|
const DefaultKeySize = 32 //256bits
|
||||||
|
var DefaultHashFunc = sha256.New
|
||||||
|
var DefaultSalt = []byte(`
|
||||||
|
(;QUHj.BQ?RXzYSO]ifkXp/G!kFmWyXyEV6Nt!d|@bo+N$L9+<d$|g6e26T}
|
||||||
|
Ao<:>SOd,6acYKY_ec+(x"R";\'4&fTAVu92GVA-wxBptOTM^2,iP5%)wnhW
|
||||||
|
hwk=]Snsgymt!3gbP2pe=J//}1a?lp9ej=&TB!C_V(cT2?z8wyoL_-13fd[]
|
||||||
|
`) //salt must be predefined in order to derive the same key
|
||||||
|
|
||||||
|
//Config stores the PBKDF2 key generation parameters
|
||||||
|
type Config struct {
|
||||||
|
Password string
|
||||||
|
Salt []byte
|
||||||
|
Iterations int
|
||||||
|
KeySize int
|
||||||
|
HashFunc func() hash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
//New creates an AES encrypted net.Conn by generating
|
||||||
|
//a key using PBKDF2 with the provided configuration
|
||||||
|
func New(conn net.Conn, c *Config) net.Conn {
|
||||||
|
//set defaults
|
||||||
|
if len(c.Salt) == 0 {
|
||||||
|
c.Salt = DefaultSalt
|
||||||
|
}
|
||||||
|
if c.Iterations == 0 {
|
||||||
|
c.Iterations = DefaultIterations
|
||||||
|
}
|
||||||
|
if c.KeySize != 16 && c.KeySize != 24 && c.KeySize != 32 {
|
||||||
|
c.KeySize = DefaultKeySize
|
||||||
|
}
|
||||||
|
if c.HashFunc == nil {
|
||||||
|
c.HashFunc = DefaultHashFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
//generate key
|
||||||
|
key := pbkdf2.Key([]byte(c.Password), c.Salt, c.Iterations, c.KeySize, c.HashFunc)
|
||||||
|
|
||||||
|
// could use scrypt, but it's a bit slow...
|
||||||
|
// dk, err := scrypt.Key([]byte(c.Password), c.Salt, 16384, 8, 1, 32)
|
||||||
|
|
||||||
|
//key will be always be the correct size so this will never error
|
||||||
|
conn, _ = NewFromKey(conn, key)
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewFromKey creates an AES encrypted net.Conn using the provided key
|
||||||
|
func NewFromKey(conn net.Conn, key []byte) (net.Conn, error) {
|
||||||
|
block, err := aes.NewCipher([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//hash(key) -> read IV
|
||||||
|
riv := DefaultHashFunc().Sum(key)
|
||||||
|
rstream := cipher.NewCFBDecrypter(block, riv[:aes.BlockSize])
|
||||||
|
reader := &cipher.StreamReader{S: rstream, R: conn}
|
||||||
|
//hash(read IV) -> write IV
|
||||||
|
wiv := DefaultHashFunc().Sum(riv)
|
||||||
|
wstream := cipher.NewCFBEncrypter(block, wiv[:aes.BlockSize])
|
||||||
|
writer := &cipher.StreamWriter{S: wstream, W: conn}
|
||||||
|
|
||||||
|
return &cryptoConn{
|
||||||
|
Conn: conn,
|
||||||
|
r: reader,
|
||||||
|
w: writer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type cryptoConn struct {
|
||||||
|
net.Conn
|
||||||
|
r io.Reader
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
//replace read and write methods
|
||||||
|
func (c *cryptoConn) Read(p []byte) (int, error) {
|
||||||
|
return c.r.Read(p)
|
||||||
|
}
|
||||||
|
func (c *cryptoConn) Write(p []byte) (int, error) {
|
||||||
|
return c.w.Write(p)
|
||||||
|
}
|
||||||
@ -7,27 +7,32 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
logger "log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/utils/id"
|
||||||
|
|
||||||
kcp "github.com/xtaci/kcp-go"
|
kcp "github.com/xtaci/kcp-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interface{})) {
|
func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interface{}), log *logger.Logger) {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@ -42,7 +47,8 @@ func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interfac
|
|||||||
log.Printf("bind crashed %s", err)
|
log.Printf("bind crashed %s", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
_, err := io.Copy(dst, src)
|
//_, err := io.Copy(dst, src)
|
||||||
|
err := ioCopy(dst, src)
|
||||||
e1 <- err
|
e1 <- err
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
@ -51,7 +57,8 @@ func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interfac
|
|||||||
log.Printf("bind crashed %s", err)
|
log.Printf("bind crashed %s", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
_, err := io.Copy(src, dst)
|
//_, err := io.Copy(src, dst)
|
||||||
|
err := ioCopy(src, dst)
|
||||||
e2 <- err
|
e2 <- err
|
||||||
}()
|
}()
|
||||||
var err interface{}
|
var err interface{}
|
||||||
@ -63,11 +70,14 @@ func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interfac
|
|||||||
}
|
}
|
||||||
src.Close()
|
src.Close()
|
||||||
dst.Close()
|
dst.Close()
|
||||||
fn(err)
|
if fn != nil {
|
||||||
|
fn(err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) {
|
func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) {
|
||||||
buf := make([]byte, 32*1024)
|
buf := LeakyBuffer.Get()
|
||||||
|
defer LeakyBuffer.Put(buf)
|
||||||
n := 0
|
n := 0
|
||||||
for {
|
for {
|
||||||
n, err = src.Read(buf)
|
n, err = src.Read(buf)
|
||||||
@ -81,14 +91,14 @@ func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TlsConnectHost(host string, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) {
|
func TlsConnectHost(host string, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) {
|
||||||
h := strings.Split(host, ":")
|
h := strings.Split(host, ":")
|
||||||
port, _ := strconv.Atoi(h[1])
|
port, _ := strconv.Atoi(h[1])
|
||||||
return TlsConnect(h[0], port, timeout, certBytes, keyBytes)
|
return TlsConnect(h[0], port, timeout, certBytes, keyBytes, caCertBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) {
|
func TlsConnect(host string, port, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) {
|
||||||
conf, err := getRequestTlsConfig(certBytes, keyBytes)
|
conf, err := getRequestTlsConfig(certBytes, keyBytes, caCertBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -98,22 +108,49 @@ func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (con
|
|||||||
}
|
}
|
||||||
return *tls.Client(_conn, conf), err
|
return *tls.Client(_conn, conf), err
|
||||||
}
|
}
|
||||||
func getRequestTlsConfig(certBytes, keyBytes []byte) (conf *tls.Config, err error) {
|
func getRequestTlsConfig(certBytes, keyBytes, caCertBytes []byte) (conf *tls.Config, err error) {
|
||||||
|
|
||||||
var cert tls.Certificate
|
var cert tls.Certificate
|
||||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serverCertPool := x509.NewCertPool()
|
serverCertPool := x509.NewCertPool()
|
||||||
ok := serverCertPool.AppendCertsFromPEM(certBytes)
|
caBytes := certBytes
|
||||||
|
if caCertBytes != nil {
|
||||||
|
caBytes = caCertBytes
|
||||||
|
|
||||||
|
}
|
||||||
|
ok := serverCertPool.AppendCertsFromPEM(caBytes)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = errors.New("failed to parse root certificate")
|
err = errors.New("failed to parse root certificate")
|
||||||
}
|
}
|
||||||
|
block, _ := pem.Decode(caBytes)
|
||||||
|
if block == nil {
|
||||||
|
panic("failed to parse certificate PEM")
|
||||||
|
}
|
||||||
|
x509Cert, _ := x509.ParseCertificate(block.Bytes)
|
||||||
|
if x509Cert == nil {
|
||||||
|
panic("failed to parse block")
|
||||||
|
}
|
||||||
conf = &tls.Config{
|
conf = &tls.Config{
|
||||||
RootCAs: serverCertPool,
|
RootCAs: serverCertPool,
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
ServerName: "proxy",
|
InsecureSkipVerify: true,
|
||||||
InsecureSkipVerify: false,
|
ServerName: x509Cert.Subject.CommonName,
|
||||||
|
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
Roots: serverCertPool,
|
||||||
|
}
|
||||||
|
for _, rawCert := range rawCerts {
|
||||||
|
cert, _ := x509.ParseCertificate(rawCert)
|
||||||
|
_, err := cert.Verify(opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -122,40 +159,23 @@ func ConnectHost(hostAndPort string, timeout int) (conn net.Conn, err error) {
|
|||||||
conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond)
|
conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func ConnectKCPHost(hostAndPort, method, key string) (conn net.Conn, err error) {
|
func ConnectKCPHost(hostAndPort string, config kcpcfg.KCPConfigArgs) (conn net.Conn, err error) {
|
||||||
kcpconn, err := kcp.DialWithOptions(hostAndPort, GetKCPBlock(method, key), 10, 3)
|
kcpconn, err := kcp.DialWithOptions(hostAndPort, config.Block, *config.DataShard, *config.ParityShard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
kcpconn.SetNoDelay(1, 10, 2, 1)
|
kcpconn.SetStreamMode(true)
|
||||||
kcpconn.SetWindowSize(1024, 1024)
|
kcpconn.SetWriteDelay(true)
|
||||||
kcpconn.SetMtu(1400)
|
kcpconn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion)
|
||||||
kcpconn.SetACKNoDelay(false)
|
kcpconn.SetMtu(*config.MTU)
|
||||||
return kcpconn, err
|
kcpconn.SetWindowSize(*config.SndWnd, *config.RcvWnd)
|
||||||
}
|
kcpconn.SetACKNoDelay(*config.AckNodelay)
|
||||||
func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) {
|
if *config.NoComp {
|
||||||
var cert tls.Certificate
|
return kcpconn, err
|
||||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
}
|
||||||
if err != nil {
|
return NewCompStream(kcpconn), err
|
||||||
return
|
|
||||||
}
|
|
||||||
clientCertPool := x509.NewCertPool()
|
|
||||||
ok := clientCertPool.AppendCertsFromPEM(certBytes)
|
|
||||||
if !ok {
|
|
||||||
err = errors.New("failed to parse root certificate")
|
|
||||||
}
|
|
||||||
config := &tls.Config{
|
|
||||||
ClientCAs: clientCertPool,
|
|
||||||
ServerName: "proxy",
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
|
||||||
}
|
|
||||||
_ln, err := tls.Listen("tcp", fmt.Sprintf("%s:%d", ip, port), config)
|
|
||||||
if err == nil {
|
|
||||||
ln = &_ln
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PathExists(_path string) bool {
|
func PathExists(_path string) bool {
|
||||||
_, err := os.Stat(_path)
|
_, err := os.Stat(_path)
|
||||||
if err != nil && os.IsNotExist(err) {
|
if err != nil && os.IsNotExist(err) {
|
||||||
@ -191,20 +211,95 @@ func CloseConn(conn *net.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func Keygen() (err error) {
|
func Keygen() (err error) {
|
||||||
cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048")
|
CList := []string{"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AR", "AT", "AU", "AZ", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BR", "BS", "BW", "BY", "BZ", "CA", "CF", "CG", "CH", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CY", "CZ", "DE", "DJ", "DK", "DO", "DZ", "EC", "EE", "EG", "ES", "ET", "FI", "FJ", "FR", "GA", "GB", "GD", "GE", "GF", "GH", "GI", "GM", "GN", "GR", "GT", "GU", "GY", "HK", "HN", "HT", "HU", "ID", "IE", "IL", "IN", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KP", "KR", "KT", "KW", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "MG", "ML", "MM", "MN", "MO", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NE", "NG", "NI", "NL", "NO", "NP", "NR", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PR", "PT", "PY", "QA", "RO", "RU", "SA", "SB", "SC", "SD", "SE", "SG", "SI", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TD", "TG", "TH", "TJ", "TM", "TN", "TO", "TR", "TT", "TW", "TZ", "UA", "UG", "US", "UY", "UZ", "VC", "VE", "VN", "YE", "YU", "ZA", "ZM", "ZR", "ZW"}
|
||||||
out, err := cmd.CombinedOutput()
|
domainSubfixList := []string{".com", ".edu", ".gov", ".int", ".mil", ".net", ".org", ".biz", ".info", ".pro", ".name", ".museum", ".coop", ".aero", ".xxx", ".idv", ".ac", ".ad", ".ae", ".af", ".ag", ".ai", ".al", ".am", ".an", ".ao", ".aq", ".ar", ".as", ".at", ".au", ".aw", ".az", ".ba", ".bb", ".bd", ".be", ".bf", ".bg", ".bh", ".bi", ".bj", ".bm", ".bn", ".bo", ".br", ".bs", ".bt", ".bv", ".bw", ".by", ".bz", ".ca", ".cc", ".cd", ".cf", ".cg", ".ch", ".ci", ".ck", ".cl", ".cm", ".cn", ".co", ".cr", ".cu", ".cv", ".cx", ".cy", ".cz", ".de", ".dj", ".dk", ".dm", ".do", ".dz", ".ec", ".ee", ".eg", ".eh", ".er", ".es", ".et", ".eu", ".fi", ".fj", ".fk", ".fm", ".fo", ".fr", ".ga", ".gd", ".ge", ".gf", ".gg", ".gh", ".gi", ".gl", ".gm", ".gn", ".gp", ".gq", ".gr", ".gs", ".gt", ".gu", ".gw", ".gy", ".hk", ".hm", ".hn", ".hr", ".ht", ".hu", ".id", ".ie", ".il", ".im", ".in", ".io", ".iq", ".ir", ".is", ".it", ".je", ".jm", ".jo", ".jp", ".ke", ".kg", ".kh", ".ki", ".km", ".kn", ".kp", ".kr", ".kw", ".ky", ".kz", ".la", ".lb", ".lc", ".li", ".lk", ".lr", ".ls", ".lt", ".lu", ".lv", ".ly", ".ma", ".mc", ".md", ".mg", ".mh", ".mk", ".ml", ".mm", ".mn", ".mo", ".mp", ".mq", ".mr", ".ms", ".mt", ".mu", ".mv", ".mw", ".mx", ".my", ".mz", ".na", ".nc", ".ne", ".nf", ".ng", ".ni", ".nl", ".no", ".np", ".nr", ".nu", ".nz", ".om", ".pa", ".pe", ".pf", ".pg", ".ph", ".pk", ".pl", ".pm", ".pn", ".pr", ".ps", ".pt", ".pw", ".py", ".qa", ".re", ".ro", ".ru", ".rw", ".sa", ".sb", ".sc", ".sd", ".se", ".sg", ".sh", ".si", ".sj", ".sk", ".sl", ".sm", ".sn", ".so", ".sr", ".st", ".sv", ".sy", ".sz", ".tc", ".td", ".tf", ".tg", ".th", ".tj", ".tk", ".tl", ".tm", ".tn", ".to", ".tp", ".tr", ".tt", ".tv", ".tw", ".tz", ".ua", ".ug", ".uk", ".um", ".us", ".uy", ".uz", ".va", ".vc", ".ve", ".vg", ".vi", ".vn", ".vu", ".wf", ".ws", ".ye", ".yt", ".yu", ".yr", ".za", ".zm", ".zw"}
|
||||||
if err != nil {
|
C := CList[int(RandInt(4))%len(CList)]
|
||||||
log.Printf("err:%s", err)
|
ST := RandString(int(RandInt(4) % 10))
|
||||||
return
|
O := RandString(int(RandInt(4) % 10))
|
||||||
|
CN := strings.ToLower(RandString(int(RandInt(4)%10)) + domainSubfixList[int(RandInt(4))%len(domainSubfixList)])
|
||||||
|
//log.Printf("C: %s, ST: %s, O: %s, CN: %s", C, ST, O, CN)
|
||||||
|
var out []byte
|
||||||
|
if len(os.Args) == 3 && os.Args[2] == "ca" {
|
||||||
|
cmd := exec.Command("sh", "-c", "openssl genrsa -out ca.key 2048")
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
|
||||||
|
cmdStr := fmt.Sprintf("openssl req -new -key ca.key -x509 -days 36500 -out ca.crt -subj /C=%s/ST=%s/O=%s/CN=%s", C, ST, O, "*."+CN)
|
||||||
|
cmd = exec.Command("sh", "-c", cmdStr)
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
} else if len(os.Args) == 5 && os.Args[2] == "ca" && os.Args[3] != "" && os.Args[4] != "" {
|
||||||
|
certBytes, _ := ioutil.ReadFile("ca.crt")
|
||||||
|
block, _ := pem.Decode(certBytes)
|
||||||
|
if block == nil || certBytes == nil {
|
||||||
|
panic("failed to parse ca certificate PEM")
|
||||||
|
}
|
||||||
|
x509Cert, _ := x509.ParseCertificate(block.Bytes)
|
||||||
|
if x509Cert == nil {
|
||||||
|
panic("failed to parse block")
|
||||||
|
}
|
||||||
|
name := os.Args[3]
|
||||||
|
days := os.Args[4]
|
||||||
|
cmd := exec.Command("sh", "-c", "openssl genrsa -out "+name+".key 2048")
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
|
||||||
|
cmdStr := fmt.Sprintf("openssl req -new -key %s.key -out %s.csr -subj /C=%s/ST=%s/O=%s/CN=%s", name, name, C, ST, O, CN)
|
||||||
|
fmt.Printf("%s", cmdStr)
|
||||||
|
cmd = exec.Command("sh", "-c", cmdStr)
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
|
||||||
|
cmdStr = fmt.Sprintf("openssl x509 -req -days %s -in %s.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out %s.crt", days, name, name)
|
||||||
|
fmt.Printf("%s", cmdStr)
|
||||||
|
cmd = exec.Command("sh", "-c", cmdStr)
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(out))
|
||||||
|
} else if len(os.Args) == 3 && os.Args[2] == "usage" {
|
||||||
|
fmt.Println(`proxy keygen //generate proxy.crt and proxy.key
|
||||||
|
proxy keygen ca //generate ca.crt and ca.key
|
||||||
|
proxy keygen ca client0 30 //generate client0.crt client0.key and use ca.crt sign it with 30 days
|
||||||
|
`)
|
||||||
|
} else if len(os.Args) == 2 {
|
||||||
|
cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048")
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
|
||||||
|
cmdStr := fmt.Sprintf("openssl req -new -key proxy.key -x509 -days 36500 -out proxy.crt -subj /C=%s/ST=%s/O=%s/CN=%s", C, ST, O, CN)
|
||||||
|
cmd = exec.Command("sh", "-c", cmdStr)
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
}
|
}
|
||||||
fmt.Println(string(out))
|
|
||||||
cmd = exec.Command("sh", "-c", `openssl req -new -key proxy.key -x509 -days 3650 -out proxy.crt -subj /C=CN/ST=BJ/O="Localhost Ltd"/CN=proxy`)
|
|
||||||
out, err = cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("err:%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println(string(out))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func GetAllInterfaceAddr() ([]net.IP, error) {
|
func GetAllInterfaceAddr() ([]net.IP, error) {
|
||||||
@ -299,9 +394,33 @@ func ReadUDPPacket(_reader io.Reader) (srcAddr string, packet []byte, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
func Uniqueid() string {
|
func Uniqueid() string {
|
||||||
var src = rand.NewSource(time.Now().UnixNano())
|
return xid.New().String()
|
||||||
s := fmt.Sprintf("%d", src.Int63())
|
// var src = rand.NewSource(time.Now().UnixNano())
|
||||||
return s[len(s)-5:len(s)-1] + fmt.Sprintf("%d", uint64(time.Now().UnixNano()))[8:]
|
// s := fmt.Sprintf("%d", src.Int63())
|
||||||
|
// return s[len(s)-5:len(s)-1] + fmt.Sprintf("%d", uint64(time.Now().UnixNano()))[8:]
|
||||||
|
}
|
||||||
|
func RandString(strlen int) string {
|
||||||
|
codes := "QWERTYUIOPLKJHGFDSAZXCVBNMabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
codeLen := len(codes)
|
||||||
|
data := make([]byte, strlen)
|
||||||
|
rand.Seed(time.Now().UnixNano() + rand.Int63() + rand.Int63() + rand.Int63() + rand.Int63())
|
||||||
|
for i := 0; i < strlen; i++ {
|
||||||
|
idx := rand.Intn(codeLen)
|
||||||
|
data[i] = byte(codes[idx])
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
func RandInt(strLen int) int64 {
|
||||||
|
codes := "123456789"
|
||||||
|
codeLen := len(codes)
|
||||||
|
data := make([]byte, strLen)
|
||||||
|
rand.Seed(time.Now().UnixNano() + rand.Int63() + rand.Int63() + rand.Int63() + rand.Int63())
|
||||||
|
for i := 0; i < strLen; i++ {
|
||||||
|
idx := rand.Intn(codeLen)
|
||||||
|
data[i] = byte(codes[idx])
|
||||||
|
}
|
||||||
|
i, _ := strconv.ParseInt(string(data), 10, 64)
|
||||||
|
return i
|
||||||
}
|
}
|
||||||
func ReadData(r io.Reader) (data string, err error) {
|
func ReadData(r io.Reader) (data string, err error) {
|
||||||
var len uint16
|
var len uint16
|
||||||
@ -383,15 +502,15 @@ func SubBytes(bytes []byte, start, end int) []byte {
|
|||||||
}
|
}
|
||||||
return bytes[start:end]
|
return bytes[start:end]
|
||||||
}
|
}
|
||||||
func TlsBytes(cert, key string) (certBytes, keyBytes []byte) {
|
func TlsBytes(cert, key string) (certBytes, keyBytes []byte, err error) {
|
||||||
certBytes, err := ioutil.ReadFile(cert)
|
certBytes, err = ioutil.ReadFile(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("err : %s", err)
|
err = fmt.Errorf("err : %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keyBytes, err = ioutil.ReadFile(key)
|
keyBytes, err = ioutil.ReadFile(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("err : %s", err)
|
err = fmt.Errorf("err : %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -428,7 +547,7 @@ func GetKCPBlock(method, key string) (block kcp.BlockCrypt) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func HttpGet(URL string, timeout int) (body []byte, code int, err error) {
|
func HttpGet(URL string, timeout int, host ...string) (body []byte, code int, err error) {
|
||||||
var tr *http.Transport
|
var tr *http.Transport
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
conf := &tls.Config{
|
conf := &tls.Config{
|
||||||
@ -442,7 +561,16 @@ func HttpGet(URL string, timeout int) (body []byte, code int, err error) {
|
|||||||
client = &http.Client{Timeout: time.Millisecond * time.Duration(timeout), Transport: tr}
|
client = &http.Client{Timeout: time.Millisecond * time.Duration(timeout), Transport: tr}
|
||||||
}
|
}
|
||||||
defer tr.CloseIdleConnections()
|
defer tr.CloseIdleConnections()
|
||||||
resp, err := client.Get(URL)
|
|
||||||
|
//resp, err := client.Get(URL)
|
||||||
|
req, err := http.NewRequest("GET", URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(host) == 1 && host[0] != "" {
|
||||||
|
req.Host = host[0]
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -451,6 +579,90 @@ func HttpGet(URL string, timeout int) (body []byte, code int, err error) {
|
|||||||
body, err = ioutil.ReadAll(resp.Body)
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func IsIternalIP(domainOrIP string, always bool) bool {
|
||||||
|
var outIPs []net.IP
|
||||||
|
var err error
|
||||||
|
var isDomain bool
|
||||||
|
if net.ParseIP(domainOrIP) == nil {
|
||||||
|
isDomain = true
|
||||||
|
}
|
||||||
|
if always && isDomain {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDomain {
|
||||||
|
outIPs, err = net.LookupIP(domainOrIP)
|
||||||
|
} else {
|
||||||
|
outIPs = []net.IP{net.ParseIP(domainOrIP)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ip := range outIPs {
|
||||||
|
if ip.IsLoopback() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ip.To4().Mask(net.IPv4Mask(255, 0, 0, 0)).String() == "10.0.0.0" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ip.To4().Mask(net.IPv4Mask(255, 0, 0, 0)).String() == "192.168.0.0" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ip.To4().Mask(net.IPv4Mask(255, 0, 0, 0)).String() == "172.0.0.0" {
|
||||||
|
i, _ := strconv.Atoi(strings.Split(ip.To4().String(), ".")[1])
|
||||||
|
return i >= 16 && i <= 31
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func IsHTTP(head []byte) bool {
|
||||||
|
keys := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"}
|
||||||
|
for _, key := range keys {
|
||||||
|
if bytes.HasPrefix(head, []byte(key)) || bytes.HasPrefix(head, []byte(strings.ToLower(key))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func IsSocks5(head []byte) bool {
|
||||||
|
if len(head) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if head[0] == uint8(0x05) && 0 < int(head[1]) && int(head[1]) < 255 {
|
||||||
|
if len(head) == 2+int(head[1]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func RemoveProxyHeaders(head []byte) []byte {
|
||||||
|
newLines := [][]byte{}
|
||||||
|
var keys = map[string]bool{}
|
||||||
|
lines := bytes.Split(head, []byte("\r\n"))
|
||||||
|
IsBody := false
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) == 0 || IsBody {
|
||||||
|
newLines = append(newLines, line)
|
||||||
|
IsBody = true
|
||||||
|
} else {
|
||||||
|
hline := bytes.SplitN(line, []byte(":"), 2)
|
||||||
|
if len(hline) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := strings.ToUpper(string(hline[0]))
|
||||||
|
if _, ok := keys[k]; ok || strings.HasPrefix(k, "PROXY-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys[k] = true
|
||||||
|
newLines = append(newLines, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes.Join(newLines, []byte("\r\n"))
|
||||||
|
}
|
||||||
|
func InsertProxyHeaders(head []byte, headers string) []byte {
|
||||||
|
return bytes.Replace(head, []byte("\r\n"), []byte("\r\n"+headers), 1)
|
||||||
|
}
|
||||||
|
|
||||||
// type sockaddr struct {
|
// type sockaddr struct {
|
||||||
// family uint16
|
// family uint16
|
||||||
|
|||||||
264
utils/id/xid.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Package xid is a globally unique id generator suited for web scale
|
||||||
|
//
|
||||||
|
// Xid is using Mongo Object ID algorithm to generate globally unique ids:
|
||||||
|
// https://docs.mongodb.org/manual/reference/object-id/
|
||||||
|
//
|
||||||
|
// - 4-byte value representing the seconds since the Unix epoch,
|
||||||
|
// - 3-byte machine identifier,
|
||||||
|
// - 2-byte process id, and
|
||||||
|
// - 3-byte counter, starting with a random value.
|
||||||
|
//
|
||||||
|
// The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
|
||||||
|
// The string representation is using base32 hex (w/o padding) for better space efficiency
|
||||||
|
// when stored in that form (20 bytes). The hex variant of base32 is used to retain the
|
||||||
|
// sortable property of the id.
|
||||||
|
//
|
||||||
|
// Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an
|
||||||
|
// issue when transported as a string between various systems. Base36 wasn't retained either
|
||||||
|
// because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned)
|
||||||
|
// and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long,
|
||||||
|
// all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`).
|
||||||
|
//
|
||||||
|
// UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between
|
||||||
|
// with 12 bytes with a more compact string representation ready for the web and no
|
||||||
|
// required configuration or central generation server.
|
||||||
|
//
|
||||||
|
// Features:
|
||||||
|
//
|
||||||
|
// - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
|
||||||
|
// - Base32 hex encoded by default (16 bytes storage when transported as printable string)
|
||||||
|
// - Non configured, you don't need set a unique machine and/or data center id
|
||||||
|
// - K-ordered
|
||||||
|
// - Embedded time with 1 second precision
|
||||||
|
// - Unicity guaranted for 16,777,216 (24 bits) unique ids per second and per host/process
|
||||||
|
//
|
||||||
|
// Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler).
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
//
|
||||||
|
// - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
|
||||||
|
// - https://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||||
|
// - https://blog.twitter.com/2010/announcing-snowflake
|
||||||
|
package xid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Code inspired from mgo/bson ObjectId
|
||||||
|
|
||||||
|
// ID represents a unique request id
|
||||||
|
type ID [rawLen]byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
encodedLen = 20 // string encoded len
|
||||||
|
decodedLen = 15 // len after base32 decoding with the padded data
|
||||||
|
rawLen = 12 // binary raw len
|
||||||
|
|
||||||
|
// encoding stores a custom version of the base32 encoding with lower case
|
||||||
|
// letters.
|
||||||
|
encoding = "0123456789abcdefghijklmnopqrstuv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidID is returned when trying to unmarshal an invalid ID
|
||||||
|
var ErrInvalidID = errors.New("xid: invalid ID")
|
||||||
|
|
||||||
|
// objectIDCounter is atomically incremented when generating a new ObjectId
|
||||||
|
// using NewObjectId() function. It's used as a counter part of an id.
|
||||||
|
// This id is initialized with a random value.
|
||||||
|
var objectIDCounter = randInt()
|
||||||
|
|
||||||
|
// machineId stores machine id generated once and used in subsequent calls
|
||||||
|
// to NewObjectId function.
|
||||||
|
var machineID = readMachineID()
|
||||||
|
|
||||||
|
// pid stores the current process id
|
||||||
|
var pid = os.Getpid()
|
||||||
|
|
||||||
|
// dec is the decoding map for base32 encoding
|
||||||
|
var dec [256]byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := 0; i < len(dec); i++ {
|
||||||
|
dec[i] = 0xFF
|
||||||
|
}
|
||||||
|
for i := 0; i < len(encoding); i++ {
|
||||||
|
dec[encoding[i]] = byte(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMachineId generates machine id and puts it into the machineId global
|
||||||
|
// variable. If this function fails to get the hostname, it will cause
|
||||||
|
// a runtime error.
|
||||||
|
func readMachineID() []byte {
|
||||||
|
id := make([]byte, 3)
|
||||||
|
if hostname, err := os.Hostname(); err == nil {
|
||||||
|
hw := md5.New()
|
||||||
|
hw.Write([]byte(hostname))
|
||||||
|
copy(id, hw.Sum(nil))
|
||||||
|
} else {
|
||||||
|
// Fallback to rand number if machine id can't be gathered
|
||||||
|
if _, randErr := rand.Reader.Read(id); randErr != nil {
|
||||||
|
panic(fmt.Errorf("xid: cannot get hostname nor generate a random number: %v; %v", err, randErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// randInt generates a random uint32
|
||||||
|
func randInt() uint32 {
|
||||||
|
b := make([]byte, 3)
|
||||||
|
if _, err := rand.Reader.Read(b); err != nil {
|
||||||
|
panic(fmt.Errorf("xid: cannot generate random number: %v;", err))
|
||||||
|
}
|
||||||
|
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// New generates a globaly unique ID
|
||||||
|
func New() ID {
|
||||||
|
var id ID
|
||||||
|
// Timestamp, 4 bytes, big endian
|
||||||
|
binary.BigEndian.PutUint32(id[:], uint32(time.Now().Unix()))
|
||||||
|
// Machine, first 3 bytes of md5(hostname)
|
||||||
|
id[4] = machineID[0]
|
||||||
|
id[5] = machineID[1]
|
||||||
|
id[6] = machineID[2]
|
||||||
|
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
|
||||||
|
id[7] = byte(pid >> 8)
|
||||||
|
id[8] = byte(pid)
|
||||||
|
// Increment, 3 bytes, big endian
|
||||||
|
i := atomic.AddUint32(&objectIDCounter, 1)
|
||||||
|
id[9] = byte(i >> 16)
|
||||||
|
id[10] = byte(i >> 8)
|
||||||
|
id[11] = byte(i)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString reads an ID from its string representation
|
||||||
|
func FromString(id string) (ID, error) {
|
||||||
|
i := &ID{}
|
||||||
|
err := i.UnmarshalText([]byte(id))
|
||||||
|
return *i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a base32 hex lowercased with no padding representation of the id (char set is 0-9, a-v).
|
||||||
|
func (id ID) String() string {
|
||||||
|
text := make([]byte, encodedLen)
|
||||||
|
encode(text, id[:])
|
||||||
|
return string(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding/text TextMarshaler interface
|
||||||
|
func (id ID) MarshalText() ([]byte, error) {
|
||||||
|
text := make([]byte, encodedLen)
|
||||||
|
encode(text, id[:])
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode by unrolling the stdlib base32 algorithm + removing all safe checks
|
||||||
|
func encode(dst, id []byte) {
|
||||||
|
dst[0] = encoding[id[0]>>3]
|
||||||
|
dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F]
|
||||||
|
dst[2] = encoding[(id[1]>>1)&0x1F]
|
||||||
|
dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F]
|
||||||
|
dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F]
|
||||||
|
dst[5] = encoding[(id[3]>>2)&0x1F]
|
||||||
|
dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F]
|
||||||
|
dst[7] = encoding[id[4]&0x1F]
|
||||||
|
dst[8] = encoding[id[5]>>3]
|
||||||
|
dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F]
|
||||||
|
dst[10] = encoding[(id[6]>>1)&0x1F]
|
||||||
|
dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F]
|
||||||
|
dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F]
|
||||||
|
dst[13] = encoding[(id[8]>>2)&0x1F]
|
||||||
|
dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F]
|
||||||
|
dst[15] = encoding[id[9]&0x1F]
|
||||||
|
dst[16] = encoding[id[10]>>3]
|
||||||
|
dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
|
||||||
|
dst[18] = encoding[(id[11]>>1)&0x1F]
|
||||||
|
dst[19] = encoding[(id[11]<<4)&0x1F]
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding/text TextUnmarshaler interface
|
||||||
|
func (id *ID) UnmarshalText(text []byte) error {
|
||||||
|
if len(text) != encodedLen {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
for _, c := range text {
|
||||||
|
if dec[c] == 0xFF {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decode(id, text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode by unrolling the stdlib base32 algorithm + removing all safe checks
|
||||||
|
func decode(id *ID, src []byte) {
|
||||||
|
id[0] = dec[src[0]]<<3 | dec[src[1]]>>2
|
||||||
|
id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4
|
||||||
|
id[2] = dec[src[3]]<<4 | dec[src[4]]>>1
|
||||||
|
id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3
|
||||||
|
id[4] = dec[src[6]]<<5 | dec[src[7]]
|
||||||
|
id[5] = dec[src[8]]<<3 | dec[src[9]]>>2
|
||||||
|
id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4
|
||||||
|
id[7] = dec[src[11]]<<4 | dec[src[12]]>>1
|
||||||
|
id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3
|
||||||
|
id[9] = dec[src[14]]<<5 | dec[src[15]]
|
||||||
|
id[10] = dec[src[16]]<<3 | dec[src[17]]>>2
|
||||||
|
id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the timestamp part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Time() time.Time {
|
||||||
|
// First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch.
|
||||||
|
secs := int64(binary.BigEndian.Uint32(id[0:4]))
|
||||||
|
return time.Unix(secs, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine returns the 3-byte machine id part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Machine() []byte {
|
||||||
|
return id[4:7]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pid returns the process id part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Pid() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(id[7:9])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter returns the incrementing value part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Counter() int32 {
|
||||||
|
b := id[9:12]
|
||||||
|
// Counter is stored as big-endian 3-byte value
|
||||||
|
return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (id ID) Value() (driver.Value, error) {
|
||||||
|
b, err := id.MarshalText()
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (id *ID) Scan(value interface{}) (err error) {
|
||||||
|
switch val := value.(type) {
|
||||||
|
case string:
|
||||||
|
return id.UnmarshalText([]byte(val))
|
||||||
|
case []byte:
|
||||||
|
return id.UnmarshalText(val)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("xid: scanning unsupported type: %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
utils/leakybuf.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Provides leaky buffer, based on the example in Effective Go.
|
||||||
|
package utils
|
||||||
|
|
||||||
|
type LeakyBuf struct {
|
||||||
|
bufSize int // size of each buffer
|
||||||
|
freeList chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeakyBufSize = 2048 // data.len(2) + hmacsha1(10) + data(4096)
|
||||||
|
const maxNBuf = 2048
|
||||||
|
|
||||||
|
var LeakyBuffer = NewLeakyBuf(maxNBuf, LeakyBufSize)
|
||||||
|
|
||||||
|
// NewLeakyBuf creates a leaky buffer which can hold at most n buffer, each
|
||||||
|
// with bufSize bytes.
|
||||||
|
func NewLeakyBuf(n, bufSize int) *LeakyBuf {
|
||||||
|
return &LeakyBuf{
|
||||||
|
bufSize: bufSize,
|
||||||
|
freeList: make(chan []byte, n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a buffer from the leaky buffer or create a new buffer.
|
||||||
|
func (lb *LeakyBuf) Get() (b []byte) {
|
||||||
|
select {
|
||||||
|
case b = <-lb.freeList:
|
||||||
|
default:
|
||||||
|
b = make([]byte, lb.bufSize)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put add the buffer into the free buffer pool for reuse. Panic if the buffer
|
||||||
|
// size is not the same with the leaky buffer's. This is intended to expose
|
||||||
|
// error usage of leaky buffer.
|
||||||
|
func (lb *LeakyBuf) Put(b []byte) {
|
||||||
|
if len(b) != lb.bufSize {
|
||||||
|
panic("invalid buffer size that's put into leaky buffer")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case lb.freeList <- b:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
145
utils/pool.go
@ -1,145 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
//ConnPool to use
|
|
||||||
type ConnPool interface {
|
|
||||||
Get() (conn interface{}, err error)
|
|
||||||
Put(conn interface{})
|
|
||||||
ReleaseAll()
|
|
||||||
Len() (length int)
|
|
||||||
}
|
|
||||||
type poolConfig struct {
|
|
||||||
Factory func() (interface{}, error)
|
|
||||||
IsActive func(interface{}) bool
|
|
||||||
Release func(interface{})
|
|
||||||
InitialCap int
|
|
||||||
MaxCap int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnPool(poolConfig poolConfig) (pool ConnPool, err error) {
|
|
||||||
p := netPool{
|
|
||||||
config: poolConfig,
|
|
||||||
conns: make(chan interface{}, poolConfig.MaxCap),
|
|
||||||
lock: &sync.Mutex{},
|
|
||||||
}
|
|
||||||
//log.Printf("pool MaxCap:%d", poolConfig.MaxCap)
|
|
||||||
if poolConfig.MaxCap > 0 {
|
|
||||||
err = p.initAutoFill(false)
|
|
||||||
if err == nil {
|
|
||||||
p.initAutoFill(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type netPool struct {
|
|
||||||
conns chan interface{}
|
|
||||||
lock *sync.Mutex
|
|
||||||
config poolConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *netPool) initAutoFill(async bool) (err error) {
|
|
||||||
var worker = func() (err error) {
|
|
||||||
for {
|
|
||||||
//log.Printf("pool fill: %v , len: %d", p.Len() <= p.config.InitialCap/2, p.Len())
|
|
||||||
if p.Len() <= p.config.InitialCap/2 {
|
|
||||||
p.lock.Lock()
|
|
||||||
errN := 0
|
|
||||||
for i := 0; i < p.config.InitialCap; i++ {
|
|
||||||
c, err := p.config.Factory()
|
|
||||||
if err != nil {
|
|
||||||
errN++
|
|
||||||
if async {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
p.lock.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case p.conns <- c:
|
|
||||||
default:
|
|
||||||
p.config.Release(c)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if p.Len() >= p.config.InitialCap {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errN > 0 {
|
|
||||||
log.Printf("fill conn pool fail , ERRN:%d", errN)
|
|
||||||
}
|
|
||||||
p.lock.Unlock()
|
|
||||||
}
|
|
||||||
if !async {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if async {
|
|
||||||
go worker()
|
|
||||||
} else {
|
|
||||||
err = worker()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *netPool) Get() (conn interface{}, err error) {
|
|
||||||
// defer func() {
|
|
||||||
// log.Printf("pool len : %d", p.Len())
|
|
||||||
// }()
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
// for {
|
|
||||||
select {
|
|
||||||
case conn = <-p.conns:
|
|
||||||
if p.config.IsActive(conn) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.config.Release(conn)
|
|
||||||
default:
|
|
||||||
conn, err = p.config.Factory()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *netPool) Put(conn interface{}) {
|
|
||||||
if conn == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
if !p.config.IsActive(conn) {
|
|
||||||
p.config.Release(conn)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case p.conns <- conn:
|
|
||||||
default:
|
|
||||||
p.config.Release(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (p *netPool) ReleaseAll() {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
close(p.conns)
|
|
||||||
for c := range p.conns {
|
|
||||||
p.config.Release(c)
|
|
||||||
}
|
|
||||||
p.conns = make(chan interface{}, p.config.InitialCap)
|
|
||||||
|
|
||||||
}
|
|
||||||
func (p *netPool) Len() (length int) {
|
|
||||||
return len(p.conns)
|
|
||||||
}
|
|
||||||
@ -1,12 +1,17 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
|
||||||
kcp "github.com/xtaci/kcp-go"
|
kcp "github.com/xtaci/kcp-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,23 +21,26 @@ type ServerChannel struct {
|
|||||||
Listener *net.Listener
|
Listener *net.Listener
|
||||||
UDPListener *net.UDPConn
|
UDPListener *net.UDPConn
|
||||||
errAcceptHandler func(err error)
|
errAcceptHandler func(err error)
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerChannel(ip string, port int) ServerChannel {
|
func NewServerChannel(ip string, port int, log *logger.Logger) ServerChannel {
|
||||||
return ServerChannel{
|
return ServerChannel{
|
||||||
ip: ip,
|
ip: ip,
|
||||||
port: port,
|
port: port,
|
||||||
|
log: log,
|
||||||
errAcceptHandler: func(err error) {
|
errAcceptHandler: func(err error) {
|
||||||
fmt.Printf("accept error , ERR:%s", err)
|
log.Printf("accept error , ERR:%s", err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func NewServerChannelHost(host string) ServerChannel {
|
func NewServerChannelHost(host string, log *logger.Logger) ServerChannel {
|
||||||
h, port, _ := net.SplitHostPort(host)
|
h, port, _ := net.SplitHostPort(host)
|
||||||
p, _ := strconv.Atoi(port)
|
p, _ := strconv.Atoi(port)
|
||||||
return ServerChannel{
|
return ServerChannel{
|
||||||
ip: h,
|
ip: h,
|
||||||
port: p,
|
port: p,
|
||||||
|
log: log,
|
||||||
errAcceptHandler: func(err error) {
|
errAcceptHandler: func(err error) {
|
||||||
log.Printf("accept error , ERR:%s", err)
|
log.Printf("accept error , ERR:%s", err)
|
||||||
},
|
},
|
||||||
@ -41,13 +49,13 @@ func NewServerChannelHost(host string) ServerChannel {
|
|||||||
func (sc *ServerChannel) SetErrAcceptHandler(fn func(err error)) {
|
func (sc *ServerChannel) SetErrAcceptHandler(fn func(err error)) {
|
||||||
sc.errAcceptHandler = fn
|
sc.errAcceptHandler = fn
|
||||||
}
|
}
|
||||||
func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.Conn)) (err error) {
|
func (sc *ServerChannel) ListenTls(certBytes, keyBytes, caCertBytes []byte, fn func(conn net.Conn)) (err error) {
|
||||||
sc.Listener, err = ListenTls(sc.ip, sc.port, certBytes, keyBytes)
|
sc.Listener, err = sc.listenTls(sc.ip, sc.port, certBytes, keyBytes, caCertBytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("ListenTls crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("ListenTls crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
@ -57,7 +65,7 @@ func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("tls connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("tls connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fn(conn)
|
fn(conn)
|
||||||
@ -72,7 +80,33 @@ func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (sc *ServerChannel) listenTls(ip string, port int, certBytes, keyBytes, caCertBytes []byte) (ln *net.Listener, err error) {
|
||||||
|
|
||||||
|
var cert tls.Certificate
|
||||||
|
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientCertPool := x509.NewCertPool()
|
||||||
|
caBytes := certBytes
|
||||||
|
if caCertBytes != nil {
|
||||||
|
caBytes = caCertBytes
|
||||||
|
}
|
||||||
|
ok := clientCertPool.AppendCertsFromPEM(caBytes)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("failed to parse root certificate")
|
||||||
|
}
|
||||||
|
config := &tls.Config{
|
||||||
|
ClientCAs: clientCertPool,
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||||
|
}
|
||||||
|
_ln, err := tls.Listen("tcp", fmt.Sprintf("%s:%d", ip, port), config)
|
||||||
|
if err == nil {
|
||||||
|
ln = &_ln
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
|
func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
|
||||||
var l net.Listener
|
var l net.Listener
|
||||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sc.ip, sc.port))
|
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sc.ip, sc.port))
|
||||||
@ -81,7 +115,7 @@ func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("ListenTCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("ListenTCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
@ -91,7 +125,7 @@ func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("tcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("tcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fn(conn)
|
fn(conn)
|
||||||
@ -113,7 +147,7 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("ListenUDP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("ListenUDP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
@ -124,7 +158,7 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("udp data handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("udp data handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fn(packet, addr, srcAddr)
|
fn(packet, addr, srcAddr)
|
||||||
@ -138,28 +172,51 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (sc *ServerChannel) ListenKCP(method, key string, fn func(conn net.Conn)) (err error) {
|
func (sc *ServerChannel) ListenKCP(config kcpcfg.KCPConfigArgs, fn func(conn net.Conn), log *logger.Logger) (err error) {
|
||||||
var l net.Listener
|
lis, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), config.Block, *config.DataShard, *config.ParityShard)
|
||||||
l, err = kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), GetKCPBlock(method, key), 10, 3)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
sc.Listener = &l
|
if err = lis.SetDSCP(*config.DSCP); err != nil {
|
||||||
|
log.Println("SetDSCP:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = lis.SetReadBuffer(*config.SockBuf); err != nil {
|
||||||
|
log.Println("SetReadBuffer:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = lis.SetWriteBuffer(*config.SockBuf); err != nil {
|
||||||
|
log.Println("SetWriteBuffer:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sc.Listener = new(net.Listener)
|
||||||
|
*sc.Listener = lis
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("ListenKCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("ListenKCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
var conn net.Conn
|
//var conn net.Conn
|
||||||
conn, err = (*sc.Listener).Accept()
|
conn, err := lis.AcceptKCP()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
log.Printf("kcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
sc.log.Printf("kcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fn(conn)
|
conn.SetStreamMode(true)
|
||||||
|
conn.SetWriteDelay(true)
|
||||||
|
conn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion)
|
||||||
|
conn.SetMtu(*config.MTU)
|
||||||
|
conn.SetWindowSize(*config.SndWnd, *config.RcvWnd)
|
||||||
|
conn.SetACKNoDelay(*config.AckNodelay)
|
||||||
|
if *config.NoComp {
|
||||||
|
fn(conn)
|
||||||
|
} else {
|
||||||
|
cconn := NewCompStream(conn)
|
||||||
|
fn(cconn)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
sc.errAcceptHandler(err)
|
sc.errAcceptHandler(err)
|
||||||
|
|||||||
173
utils/sni/sni.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package sni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServerNameFromBytes(data []byte) (sn string, err error) {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
bufferedReader := bufio.NewReader(reader)
|
||||||
|
c := bufferedConn{bufferedReader, nil, nil}
|
||||||
|
sn, _, err = ServerNameFromConn(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type bufferedConn struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
rout io.Reader
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufferedConn(c net.Conn) bufferedConn {
|
||||||
|
return bufferedConn{bufio.NewReader(c), nil, c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bufferedConn) Peek(n int) ([]byte, error) {
|
||||||
|
return b.r.Peek(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bufferedConn) Read(p []byte) (int, error) {
|
||||||
|
if b.rout != nil {
|
||||||
|
return b.rout.Read(p)
|
||||||
|
}
|
||||||
|
return b.r.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var malformedError = errors.New("malformed client hello")
|
||||||
|
|
||||||
|
func getHello(b []byte) (string, error) {
|
||||||
|
rest := b[5:]
|
||||||
|
|
||||||
|
if len(rest) == 0 {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
current := 0
|
||||||
|
handshakeType := rest[0]
|
||||||
|
current += 1
|
||||||
|
if handshakeType != 0x1 {
|
||||||
|
return "", errors.New("Not a ClientHello")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over another length
|
||||||
|
current += 3
|
||||||
|
// Skip over protocolversion
|
||||||
|
current += 2
|
||||||
|
// Skip over random number
|
||||||
|
current += 4 + 28
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over session ID
|
||||||
|
sessionIDLength := int(rest[current])
|
||||||
|
current += 1
|
||||||
|
current += sessionIDLength
|
||||||
|
|
||||||
|
if current+1 > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
current += cipherSuiteLength
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
compressionMethodLength := int(rest[current])
|
||||||
|
current += 1
|
||||||
|
current += compressionMethodLength
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", errors.New("no extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
hostname := ""
|
||||||
|
for current+4 < len(rest) && hostname == "" {
|
||||||
|
extensionType := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
extensionDataLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
if extensionType == 0 {
|
||||||
|
|
||||||
|
// Skip over number of names as we're assuming there's just one
|
||||||
|
current += 2
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
nameType := rest[current]
|
||||||
|
current += 1
|
||||||
|
if nameType != 0 {
|
||||||
|
return "", errors.New("Not a hostname")
|
||||||
|
}
|
||||||
|
if current+1 > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
nameLen := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
if current+nameLen > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
hostname = string(rest[current : current+nameLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
current += extensionDataLength
|
||||||
|
}
|
||||||
|
if hostname == "" {
|
||||||
|
return "", errors.New("No hostname")
|
||||||
|
}
|
||||||
|
return hostname, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHelloBytes(c bufferedConn) ([]byte, error) {
|
||||||
|
b, err := c.Peek(5)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != 0x16 {
|
||||||
|
return []byte{}, errors.New("not TLS")
|
||||||
|
}
|
||||||
|
|
||||||
|
restLengthBytes := b[3:]
|
||||||
|
restLength := (int(restLengthBytes[0]) << 8) + int(restLengthBytes[1])
|
||||||
|
|
||||||
|
return c.Peek(5 + restLength)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServername(c bufferedConn) (string, []byte, error) {
|
||||||
|
all, err := getHelloBytes(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
name, err := getHello(all)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return name, all, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uses SNI to get the name of the server from the connection. Returns the ServerName and a buffered connection that will not have been read off of.
|
||||||
|
func ServerNameFromConn(c net.Conn) (string, net.Conn, error) {
|
||||||
|
bufconn := newBufferedConn(c)
|
||||||
|
sn, helloBytes, err := getServername(bufconn)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
bufconn.rout = io.MultiReader(bytes.NewBuffer(helloBytes), c)
|
||||||
|
return sn, bufconn, nil
|
||||||
|
}
|
||||||
253
utils/socks/client.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package socks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var socks5Errors = []string{
|
||||||
|
"",
|
||||||
|
"general failure",
|
||||||
|
"connection forbidden",
|
||||||
|
"network unreachable",
|
||||||
|
"host unreachable",
|
||||||
|
"connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"command not supported",
|
||||||
|
"address type not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
User, Password string
|
||||||
|
}
|
||||||
|
type ClientConn struct {
|
||||||
|
user string
|
||||||
|
password string
|
||||||
|
conn *net.Conn
|
||||||
|
header []byte
|
||||||
|
timeout time.Duration
|
||||||
|
addr string
|
||||||
|
network string
|
||||||
|
udpAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
|
// target must be a canonical address with a host and port.
|
||||||
|
// network : tcp udp
|
||||||
|
func NewClientConn(conn *net.Conn, network, target string, timeout time.Duration, auth *Auth, header []byte) *ClientConn {
|
||||||
|
s := &ClientConn{
|
||||||
|
conn: conn,
|
||||||
|
network: network,
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
if auth != nil {
|
||||||
|
s.user = auth.User
|
||||||
|
s.password = auth.Password
|
||||||
|
}
|
||||||
|
if header != nil && len(header) > 0 {
|
||||||
|
s.header = header
|
||||||
|
}
|
||||||
|
if network == "udp" && target == "" {
|
||||||
|
target = "0.0.0.0:1"
|
||||||
|
}
|
||||||
|
s.addr = target
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
|
// and commands the server to extend that connection to target,
|
||||||
|
// which must be a canonical address with a host and port.
|
||||||
|
func (s *ClientConn) Handshake() error {
|
||||||
|
host, portStr, err := net.SplitHostPort(s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||||
|
}
|
||||||
|
if port < 1 || port > 0xffff {
|
||||||
|
return errors.New("proxy: port number out of range: " + portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.handshake(host); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf := []byte{}
|
||||||
|
if s.network == "tcp" {
|
||||||
|
buf = append(buf, VERSION_V5, CMD_CONNECT, 0 /* reserved */)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
buf = append(buf, VERSION_V5, CMD_ASSOCIATE, 0 /* reserved */)
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
buf = append(buf, ATYP_IPV4)
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
buf = append(buf, ATYP_IPV6)
|
||||||
|
}
|
||||||
|
buf = append(buf, ip...)
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
return errors.New("proxy: destination host name too long: " + host)
|
||||||
|
}
|
||||||
|
buf = append(buf, ATYP_DOMAIN)
|
||||||
|
buf = append(buf, byte(len(host)))
|
||||||
|
buf = append(buf, host...)
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(port>>8), byte(port))
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := (*s.conn).Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := io.ReadFull((*s.conn), buf[:4]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
failure := "unknown error"
|
||||||
|
if int(buf[1]) < len(socks5Errors) {
|
||||||
|
failure = socks5Errors[buf[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failure) > 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToDiscard := 0
|
||||||
|
switch buf[3] {
|
||||||
|
case ATYP_IPV4:
|
||||||
|
bytesToDiscard = net.IPv4len
|
||||||
|
case ATYP_IPV6:
|
||||||
|
bytesToDiscard = net.IPv6len
|
||||||
|
case ATYP_DOMAIN:
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
_, err := io.ReadFull((*s.conn), buf[:1])
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
bytesToDiscard = int(buf[0])
|
||||||
|
default:
|
||||||
|
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(buf) < bytesToDiscard {
|
||||||
|
buf = make([]byte, bytesToDiscard)
|
||||||
|
} else {
|
||||||
|
buf = buf[:bytesToDiscard]
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := io.ReadFull((*s.conn), buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
var ip net.IP
|
||||||
|
ip = buf
|
||||||
|
ipStr := ""
|
||||||
|
if bytesToDiscard == net.IPv4len || bytesToDiscard == net.IPv6len {
|
||||||
|
if ipv4 := ip.To4(); ipv4 != nil {
|
||||||
|
ipStr = ipv4.String()
|
||||||
|
} else {
|
||||||
|
ipStr = ip.To16().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//log.Printf("%v", ipStr)
|
||||||
|
// Also need to discard the port number
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
p := binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
|
||||||
|
//log.Printf("%v", p)
|
||||||
|
s.udpAddr = net.JoinHostPort(ipStr, fmt.Sprintf("%d", p))
|
||||||
|
//log.Printf("%v", s.udpAddr)
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *ClientConn) SendUDP(data []byte, addr string) (respData []byte, err error) {
|
||||||
|
|
||||||
|
c, err := net.DialTimeout("udp", s.udpAddr, s.timeout)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn := c.(*net.UDPConn)
|
||||||
|
|
||||||
|
p := NewPacketUDP()
|
||||||
|
p.Build(addr, data)
|
||||||
|
conn.SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
conn.Write(p.Bytes())
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
conn.SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
n, _, err := conn.ReadFrom(buf)
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respData = buf[:n]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *ClientConn) handshake(host string) error {
|
||||||
|
|
||||||
|
// the size here is just an estimate
|
||||||
|
buf := make([]byte, 0, 6+len(host))
|
||||||
|
|
||||||
|
buf = append(buf, VERSION_V5)
|
||||||
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
|
buf = append(buf, 2 /* num auth methods */, Method_NO_AUTH, Method_USER_PASS)
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 1 /* num auth methods */, Method_NO_AUTH)
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := (*s.conn).Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
if buf[0] != 5 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
|
}
|
||||||
|
if buf[1] == 0xff {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
|
if buf[1] == Method_USER_PASS {
|
||||||
|
buf = buf[:0]
|
||||||
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
buf = append(buf, uint8(len(s.user)))
|
||||||
|
buf = append(buf, s.user...)
|
||||||
|
buf = append(buf, uint8(len(s.password)))
|
||||||
|
buf = append(buf, s.password...)
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := (*s.conn).Write(buf); err != nil {
|
||||||
|
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(s.timeout))
|
||||||
|
if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil {
|
||||||
|
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
if buf[1] != 0 {
|
||||||
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
226
utils/socks/server.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package socks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/snail007/goproxy/utils"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Method_NO_AUTH = uint8(0x00)
|
||||||
|
Method_GSSAPI = uint8(0x01)
|
||||||
|
Method_USER_PASS = uint8(0x02)
|
||||||
|
Method_IANA = uint8(0x7F)
|
||||||
|
Method_RESVERVE = uint8(0x80)
|
||||||
|
Method_NONE_ACCEPTABLE = uint8(0xFF)
|
||||||
|
VERSION_V5 = uint8(0x05)
|
||||||
|
CMD_CONNECT = uint8(0x01)
|
||||||
|
CMD_BIND = uint8(0x02)
|
||||||
|
CMD_ASSOCIATE = uint8(0x03)
|
||||||
|
ATYP_IPV4 = uint8(0x01)
|
||||||
|
ATYP_DOMAIN = uint8(0x03)
|
||||||
|
ATYP_IPV6 = uint8(0x04)
|
||||||
|
REP_SUCCESS = uint8(0x00)
|
||||||
|
REP_REQ_FAIL = uint8(0x01)
|
||||||
|
REP_RULE_FORBIDDEN = uint8(0x02)
|
||||||
|
REP_NETWOR_UNREACHABLE = uint8(0x03)
|
||||||
|
REP_HOST_UNREACHABLE = uint8(0x04)
|
||||||
|
REP_CONNECTION_REFUSED = uint8(0x05)
|
||||||
|
REP_TTL_TIMEOUT = uint8(0x06)
|
||||||
|
REP_CMD_UNSUPPORTED = uint8(0x07)
|
||||||
|
REP_ATYP_UNSUPPORTED = uint8(0x08)
|
||||||
|
REP_UNKNOWN = uint8(0x09)
|
||||||
|
RSV = uint8(0x00)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00}
|
||||||
|
ZERO_PORT = []byte{0x00, 0x00}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
target string
|
||||||
|
user string
|
||||||
|
password string
|
||||||
|
conn *net.Conn
|
||||||
|
timeout time.Duration
|
||||||
|
auth *utils.BasicAuth
|
||||||
|
header []byte
|
||||||
|
ver uint8
|
||||||
|
//method
|
||||||
|
methodsCount uint8
|
||||||
|
methods []uint8
|
||||||
|
method uint8
|
||||||
|
//request
|
||||||
|
cmd uint8
|
||||||
|
reserve uint8
|
||||||
|
addressType uint8
|
||||||
|
dstAddr string
|
||||||
|
dstPort string
|
||||||
|
dstHost string
|
||||||
|
udpAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerConn(conn *net.Conn, timeout time.Duration, auth *utils.BasicAuth, udpAddress string, header []byte) *ServerConn {
|
||||||
|
if udpAddress == "" {
|
||||||
|
udpAddress = "0.0.0.0:16666"
|
||||||
|
}
|
||||||
|
s := &ServerConn{
|
||||||
|
conn: conn,
|
||||||
|
timeout: timeout,
|
||||||
|
auth: auth,
|
||||||
|
header: header,
|
||||||
|
ver: VERSION_V5,
|
||||||
|
udpAddress: udpAddress,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *ServerConn) Close() {
|
||||||
|
utils.CloseConn(s.conn)
|
||||||
|
}
|
||||||
|
func (s *ServerConn) AuthData() Auth {
|
||||||
|
return Auth{s.user, s.password}
|
||||||
|
}
|
||||||
|
func (s *ServerConn) Method() uint8 {
|
||||||
|
return s.method
|
||||||
|
}
|
||||||
|
func (s *ServerConn) Target() string {
|
||||||
|
return s.target
|
||||||
|
}
|
||||||
|
func (s *ServerConn) Handshake() (err error) {
|
||||||
|
remoteAddr := (*s.conn).RemoteAddr()
|
||||||
|
//协商开始
|
||||||
|
//method select request
|
||||||
|
var methodReq MethodsRequest
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
|
||||||
|
methodReq, e := NewMethodsRequest((*s.conn), s.header)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
if e != nil {
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
methodReq.Reply(Method_NONE_ACCEPTABLE)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
err = fmt.Errorf("new methods request fail,ERR: %s", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//log.Printf("%v,s.auth == %v && methodReq.Select(Method_NO_AUTH) %v", methodReq.methods, s.auth, methodReq.Select(Method_NO_AUTH))
|
||||||
|
if s.auth == nil && methodReq.Select(Method_NO_AUTH) && !methodReq.Select(Method_USER_PASS) {
|
||||||
|
// if !methodReq.Select(Method_NO_AUTH) {
|
||||||
|
// (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
// methodReq.Reply(Method_NONE_ACCEPTABLE)
|
||||||
|
// (*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
// err = fmt.Errorf("none method found : Method_NO_AUTH")
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
s.method = Method_NO_AUTH
|
||||||
|
//method select reply
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
err = methodReq.Reply(Method_NO_AUTH)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("reply answer data fail,ERR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// err = fmt.Errorf("% x", methodReq.Bytes())
|
||||||
|
} else {
|
||||||
|
//auth
|
||||||
|
if !methodReq.Select(Method_USER_PASS) {
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
methodReq.Reply(Method_NONE_ACCEPTABLE)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
err = fmt.Errorf("none method found : Method_USER_PASS")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.method = Method_USER_PASS
|
||||||
|
//method reply need auth
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
err = methodReq.Reply(Method_USER_PASS)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("reply answer data fail,ERR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//read auth
|
||||||
|
buf := make([]byte, 500)
|
||||||
|
var n int
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
n, err = (*s.conn).Read(buf)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read auth info fail,ERR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r := buf[:n]
|
||||||
|
s.user = string(r[2 : r[1]+2])
|
||||||
|
s.password = string(r[2+r[1]+1:])
|
||||||
|
//err = fmt.Errorf("user:%s,pass:%s", user, pass)
|
||||||
|
//auth
|
||||||
|
_addr := strings.Split(remoteAddr.String(), ":")
|
||||||
|
if s.auth == nil || s.auth.CheckUserPass(s.user, s.password, _addr[0], "") {
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout)))
|
||||||
|
_, err = (*s.conn).Write([]byte{0x01, 0x00})
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("answer auth success to %s fail,ERR: %s", remoteAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout)))
|
||||||
|
_, err = (*s.conn).Write([]byte{0x01, 0x01})
|
||||||
|
(*s.conn).SetDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("answer auth fail to %s fail,ERR: %s", remoteAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("auth fail from %s", remoteAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//request detail
|
||||||
|
(*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout))
|
||||||
|
request, e := NewRequest(*s.conn)
|
||||||
|
(*s.conn).SetReadDeadline(time.Time{})
|
||||||
|
if e != nil {
|
||||||
|
err = fmt.Errorf("read request data fail,ERR: %s", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//协商结束
|
||||||
|
|
||||||
|
switch request.CMD() {
|
||||||
|
case CMD_BIND:
|
||||||
|
err = request.TCPReply(REP_UNKNOWN)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("TCPReply REP_UNKNOWN to %s fail,ERR: %s", remoteAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("cmd bind not supported, form: %s", remoteAddr)
|
||||||
|
return
|
||||||
|
case CMD_CONNECT:
|
||||||
|
err = request.TCPReply(REP_SUCCESS)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("TCPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case CMD_ASSOCIATE:
|
||||||
|
err = request.UDPReply(REP_SUCCESS, s.udpAddress)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("UDPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fill socks info
|
||||||
|
s.target = request.Addr()
|
||||||
|
s.methodsCount = methodReq.MethodsCount()
|
||||||
|
s.methods = methodReq.Methods()
|
||||||
|
s.cmd = request.CMD()
|
||||||
|
s.reserve = request.reserve
|
||||||
|
s.addressType = request.addressType
|
||||||
|
s.dstAddr = request.dstAddr
|
||||||
|
s.dstHost = request.dstHost
|
||||||
|
s.dstPort = request.dstPort
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -3,44 +3,13 @@ package socks
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
Method_NO_AUTH = uint8(0x00)
|
|
||||||
Method_GSSAPI = uint8(0x01)
|
|
||||||
Method_USER_PASS = uint8(0x02)
|
|
||||||
Method_IANA = uint8(0x7F)
|
|
||||||
Method_RESVERVE = uint8(0x80)
|
|
||||||
Method_NONE_ACCEPTABLE = uint8(0xFF)
|
|
||||||
VERSION_V5 = uint8(0x05)
|
|
||||||
CMD_CONNECT = uint8(0x01)
|
|
||||||
CMD_BIND = uint8(0x02)
|
|
||||||
CMD_ASSOCIATE = uint8(0x03)
|
|
||||||
ATYP_IPV4 = uint8(0x01)
|
|
||||||
ATYP_DOMAIN = uint8(0x03)
|
|
||||||
ATYP_IPV6 = uint8(0x04)
|
|
||||||
REP_SUCCESS = uint8(0x00)
|
|
||||||
REP_REQ_FAIL = uint8(0x01)
|
|
||||||
REP_RULE_FORBIDDEN = uint8(0x02)
|
|
||||||
REP_NETWOR_UNREACHABLE = uint8(0x03)
|
|
||||||
REP_HOST_UNREACHABLE = uint8(0x04)
|
|
||||||
REP_CONNECTION_REFUSED = uint8(0x05)
|
|
||||||
REP_TTL_TIMEOUT = uint8(0x06)
|
|
||||||
REP_CMD_UNSUPPORTED = uint8(0x07)
|
|
||||||
REP_ATYP_UNSUPPORTED = uint8(0x08)
|
|
||||||
REP_UNKNOWN = uint8(0x09)
|
|
||||||
RSV = uint8(0x00)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00}
|
|
||||||
ZERO_PORT = []byte{0x00, 0x00}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
ver uint8
|
ver uint8
|
||||||
cmd uint8
|
cmd uint8
|
||||||
@ -53,20 +22,24 @@ type Request struct {
|
|||||||
rw io.ReadWriter
|
rw io.ReadWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequest(rw io.ReadWriter) (req Request, err interface{}) {
|
func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{}) {
|
||||||
var b [1024]byte
|
var b = make([]byte, 1024)
|
||||||
var n int
|
var n int
|
||||||
req = Request{rw: rw}
|
req = Request{rw: rw}
|
||||||
n, err = rw.Read(b[:])
|
if header != nil && len(header) == 1 && len(header[0]) > 1 {
|
||||||
if err != nil {
|
b = header[0]
|
||||||
err = fmt.Errorf("read req data fail,ERR: %s", err)
|
n = len(header[0])
|
||||||
return
|
} else {
|
||||||
|
n, err = rw.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read req data fail,ERR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req.ver = uint8(b[0])
|
req.ver = uint8(b[0])
|
||||||
req.cmd = uint8(b[1])
|
req.cmd = uint8(b[1])
|
||||||
req.reserve = uint8(b[2])
|
req.reserve = uint8(b[2])
|
||||||
req.addressType = uint8(b[3])
|
req.addressType = uint8(b[3])
|
||||||
|
|
||||||
if b[0] != 0x5 {
|
if b[0] != 0x5 {
|
||||||
err = fmt.Errorf("sosck version supported")
|
err = fmt.Errorf("sosck version supported")
|
||||||
req.TCPReply(REP_REQ_FAIL)
|
req.TCPReply(REP_REQ_FAIL)
|
||||||
@ -124,7 +97,7 @@ func (s *Request) NewReply(rep uint8, addr string) []byte {
|
|||||||
ipv6[4], ipv6[5], ipv6[6], ipv6[7],
|
ipv6[4], ipv6[5], ipv6[6], ipv6[7],
|
||||||
ipv6[8], ipv6[9], ipv6[10], ipv6[11],
|
ipv6[8], ipv6[9], ipv6[10], ipv6[11],
|
||||||
)
|
)
|
||||||
if ipv6 != nil && "0000000000255255" != zeroiIPv6 {
|
if ipb == nil && ipv6 != nil && "0000000000255255" != zeroiIPv6 {
|
||||||
atyp = ATYP_IPV6
|
atyp = ATYP_IPV6
|
||||||
ipb = ip.To16()
|
ipb = ip.To16()
|
||||||
}
|
}
|
||||||
@ -150,7 +123,7 @@ type MethodsRequest struct {
|
|||||||
rw *io.ReadWriter
|
rw *io.ReadWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMethodsRequest(r io.ReadWriter) (s MethodsRequest, err interface{}) {
|
func NewMethodsRequest(r io.ReadWriter, header ...[]byte) (s MethodsRequest, err interface{}) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = recover()
|
err = recover()
|
||||||
@ -160,9 +133,14 @@ func NewMethodsRequest(r io.ReadWriter) (s MethodsRequest, err interface{}) {
|
|||||||
s.rw = &r
|
s.rw = &r
|
||||||
var buf = make([]byte, 300)
|
var buf = make([]byte, 300)
|
||||||
var n int
|
var n int
|
||||||
n, err = r.Read(buf)
|
if header != nil && len(header) == 1 && len(header[0]) > 1 {
|
||||||
if err != nil {
|
buf = header[0]
|
||||||
return
|
n = len(header[0])
|
||||||
|
} else {
|
||||||
|
n, err = r.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if buf[0] != 0x05 {
|
if buf[0] != 0x05 {
|
||||||
err = fmt.Errorf("socks version not supported")
|
err = fmt.Errorf("socks version not supported")
|
||||||
@ -172,7 +150,6 @@ func NewMethodsRequest(r io.ReadWriter) (s MethodsRequest, err interface{}) {
|
|||||||
err = fmt.Errorf("socks methods data length error")
|
err = fmt.Errorf("socks methods data length error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ver = buf[0]
|
s.ver = buf[0]
|
||||||
s.methodsCount = buf[1]
|
s.methodsCount = buf[1]
|
||||||
s.methods = buf[2:n]
|
s.methods = buf[2:n]
|
||||||
@ -185,6 +162,9 @@ func (s *MethodsRequest) Version() uint8 {
|
|||||||
func (s *MethodsRequest) MethodsCount() uint8 {
|
func (s *MethodsRequest) MethodsCount() uint8 {
|
||||||
return s.methodsCount
|
return s.methodsCount
|
||||||
}
|
}
|
||||||
|
func (s *MethodsRequest) Methods() []uint8 {
|
||||||
|
return s.methods
|
||||||
|
}
|
||||||
func (s *MethodsRequest) Select(method uint8) bool {
|
func (s *MethodsRequest) Select(method uint8) bool {
|
||||||
for _, m := range s.methods {
|
for _, m := range s.methods {
|
||||||
if m == method {
|
if m == method {
|
||||||
@ -201,17 +181,6 @@ func (s *MethodsRequest) Bytes() []byte {
|
|||||||
return s.bytes
|
return s.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDPPacket struct {
|
|
||||||
rsv uint16
|
|
||||||
frag uint8
|
|
||||||
atype uint8
|
|
||||||
dstHost string
|
|
||||||
dstPort string
|
|
||||||
data []byte
|
|
||||||
header []byte
|
|
||||||
bytes []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseUDPPacket(b []byte) (p UDPPacket, err error) {
|
func ParseUDPPacket(b []byte) (p UDPPacket, err error) {
|
||||||
p = UDPPacket{}
|
p = UDPPacket{}
|
||||||
p.frag = uint8(b[2])
|
p.frag = uint8(b[2])
|
||||||
@ -239,6 +208,18 @@ func ParseUDPPacket(b []byte) (p UDPPacket, err error) {
|
|||||||
p.header = b[:portIndex+2]
|
p.header = b[:portIndex+2]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UDPPacket struct {
|
||||||
|
rsv uint16
|
||||||
|
frag uint8
|
||||||
|
atype uint8
|
||||||
|
dstHost string
|
||||||
|
dstPort string
|
||||||
|
data []byte
|
||||||
|
header []byte
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UDPPacket) Header() []byte {
|
func (s *UDPPacket) Header() []byte {
|
||||||
return s.header
|
return s.header
|
||||||
}
|
}
|
||||||
@ -258,3 +239,104 @@ func (s *UDPPacket) Port() string {
|
|||||||
func (s *UDPPacket) Data() []byte {
|
func (s *UDPPacket) Data() []byte {
|
||||||
return s.data
|
return s.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketUDP struct {
|
||||||
|
rsv uint16
|
||||||
|
frag uint8
|
||||||
|
atype uint8
|
||||||
|
dstHost string
|
||||||
|
dstPort string
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketUDP() (p PacketUDP) {
|
||||||
|
return PacketUDP{}
|
||||||
|
}
|
||||||
|
func (p *PacketUDP) Build(destAddr string, data []byte) (err error) {
|
||||||
|
host, port, err := net.SplitHostPort(destAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.rsv = 0
|
||||||
|
p.frag = 0
|
||||||
|
p.dstHost = host
|
||||||
|
p.dstPort = port
|
||||||
|
p.atype = ATYP_IPV4
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
p.atype = ATYP_IPV4
|
||||||
|
ip = ip4
|
||||||
|
} else {
|
||||||
|
p.atype = ATYP_IPV6
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(host) > 255 {
|
||||||
|
err = errors.New("proxy: destination host name too long: " + host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.atype = ATYP_DOMAIN
|
||||||
|
}
|
||||||
|
p.data = data
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (p *PacketUDP) Parse(b []byte) (err error) {
|
||||||
|
p.frag = uint8(b[2])
|
||||||
|
if p.frag != 0 {
|
||||||
|
err = fmt.Errorf("FRAG only support for 0 , %v ,%v", p.frag, b[:4])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
portIndex := 0
|
||||||
|
p.atype = b[3]
|
||||||
|
switch p.atype {
|
||||||
|
case ATYP_IPV4: //IP V4
|
||||||
|
p.dstHost = net.IPv4(b[4], b[5], b[6], b[7]).String()
|
||||||
|
portIndex = 8
|
||||||
|
case ATYP_DOMAIN: //域名
|
||||||
|
domainLen := uint8(b[4])
|
||||||
|
p.dstHost = string(b[5 : 5+domainLen]) //b[4]表示域名的长度
|
||||||
|
portIndex = int(5 + domainLen)
|
||||||
|
case ATYP_IPV6: //IP V6
|
||||||
|
p.dstHost = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String()
|
||||||
|
portIndex = 20
|
||||||
|
}
|
||||||
|
p.dstPort = strconv.Itoa(int(b[portIndex])<<8 | int(b[portIndex+1]))
|
||||||
|
p.data = b[portIndex+2:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (p *PacketUDP) Header() []byte {
|
||||||
|
header := new(bytes.Buffer)
|
||||||
|
header.Write([]byte{0x00, 0x00, p.frag, p.atype})
|
||||||
|
if p.atype == ATYP_IPV4 {
|
||||||
|
ip := net.ParseIP(p.dstHost)
|
||||||
|
header.Write(ip.To4())
|
||||||
|
} else if p.atype == ATYP_IPV6 {
|
||||||
|
ip := net.ParseIP(p.dstHost)
|
||||||
|
header.Write(ip.To16())
|
||||||
|
} else if p.atype == ATYP_DOMAIN {
|
||||||
|
hBytes := []byte(p.dstHost)
|
||||||
|
header.WriteByte(byte(len(hBytes)))
|
||||||
|
header.Write(hBytes)
|
||||||
|
}
|
||||||
|
port, _ := strconv.ParseUint(p.dstPort, 10, 64)
|
||||||
|
portBytes := new(bytes.Buffer)
|
||||||
|
binary.Write(portBytes, binary.BigEndian, port)
|
||||||
|
header.Write(portBytes.Bytes()[portBytes.Len()-2:])
|
||||||
|
return header.Bytes()
|
||||||
|
}
|
||||||
|
func (p *PacketUDP) Bytes() []byte {
|
||||||
|
packBytes := new(bytes.Buffer)
|
||||||
|
packBytes.Write(p.Header())
|
||||||
|
packBytes.Write(p.data)
|
||||||
|
return packBytes.Bytes()
|
||||||
|
}
|
||||||
|
func (p *PacketUDP) Host() string {
|
||||||
|
return p.dstHost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PacketUDP) Port() string {
|
||||||
|
return p.dstPort
|
||||||
|
}
|
||||||
|
func (p *PacketUDP) Data() []byte {
|
||||||
|
return p.data
|
||||||
|
}
|
||||||
|
|||||||
686
utils/structs.go
@ -1,19 +1,26 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
logger "log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/snail007/goproxy/services/kcpcfg"
|
||||||
|
"github.com/snail007/goproxy/utils/sni"
|
||||||
|
|
||||||
|
"github.com/golang/snappy"
|
||||||
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Checker struct {
|
type Checker struct {
|
||||||
@ -22,6 +29,8 @@ type Checker struct {
|
|||||||
directMap ConcurrentMap
|
directMap ConcurrentMap
|
||||||
interval int64
|
interval int64
|
||||||
timeout int
|
timeout int
|
||||||
|
isStop bool
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
type CheckerItem struct {
|
type CheckerItem struct {
|
||||||
IsHTTPS bool
|
IsHTTPS bool
|
||||||
@ -37,11 +46,13 @@ type CheckerItem struct {
|
|||||||
//NewChecker args:
|
//NewChecker args:
|
||||||
//timeout : tcp timeout milliseconds ,connect to host
|
//timeout : tcp timeout milliseconds ,connect to host
|
||||||
//interval: recheck domain interval seconds
|
//interval: recheck domain interval seconds
|
||||||
func NewChecker(timeout int, interval int64, blockedFile, directFile string) Checker {
|
func NewChecker(timeout int, interval int64, blockedFile, directFile string, log *logger.Logger) Checker {
|
||||||
ch := Checker{
|
ch := Checker{
|
||||||
data: NewConcurrentMap(),
|
data: NewConcurrentMap(),
|
||||||
interval: interval,
|
interval: interval,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
isStop: false,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
ch.blockedMap = ch.loadMap(blockedFile)
|
ch.blockedMap = ch.loadMap(blockedFile)
|
||||||
ch.directMap = ch.loadMap(directFile)
|
ch.directMap = ch.loadMap(directFile)
|
||||||
@ -51,7 +62,10 @@ func NewChecker(timeout int, interval int64, blockedFile, directFile string) Che
|
|||||||
if !ch.directMap.IsEmpty() {
|
if !ch.directMap.IsEmpty() {
|
||||||
log.Printf("direct file loaded , domains : %d", ch.directMap.Count())
|
log.Printf("direct file loaded , domains : %d", ch.directMap.Count())
|
||||||
}
|
}
|
||||||
ch.start()
|
if interval > 0 {
|
||||||
|
ch.start()
|
||||||
|
}
|
||||||
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +74,7 @@ func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) {
|
|||||||
if PathExists(f) {
|
if PathExists(f) {
|
||||||
_contents, err := ioutil.ReadFile(f)
|
_contents, err := ioutil.ReadFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("load file err:%s", err)
|
c.log.Printf("load file err:%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, line := range strings.Split(string(_contents), "\n") {
|
for _, line := range strings.Split(string(_contents), "\n") {
|
||||||
@ -72,23 +86,24 @@ func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (c *Checker) Stop() {
|
||||||
|
c.isStop = true
|
||||||
|
}
|
||||||
func (c *Checker) start() {
|
func (c *Checker) start() {
|
||||||
go func() {
|
go func() {
|
||||||
|
//log.Printf("checker started")
|
||||||
for {
|
for {
|
||||||
|
//log.Printf("checker did")
|
||||||
for _, v := range c.data.Items() {
|
for _, v := range c.data.Items() {
|
||||||
go func(item CheckerItem) {
|
go func(item CheckerItem) {
|
||||||
if c.isNeedCheck(item) {
|
if c.isNeedCheck(item) {
|
||||||
//log.Printf("check %s", item.Domain)
|
//log.Printf("check %s", item.Host)
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
var err error
|
||||||
if item.IsHTTPS {
|
conn, err = ConnectHost(item.Host, c.timeout)
|
||||||
conn, err = ConnectHost(item.Host, c.timeout)
|
if err == nil {
|
||||||
if err == nil {
|
conn.SetDeadline(time.Now().Add(time.Millisecond))
|
||||||
conn.SetDeadline(time.Now().Add(time.Millisecond))
|
conn.Close()
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = HTTPGet(item.URL, c.timeout)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
item.FailCount = item.FailCount + 1
|
item.FailCount = item.FailCount + 1
|
||||||
@ -100,6 +115,9 @@ func (c *Checker) start() {
|
|||||||
}(v.(CheckerItem))
|
}(v.(CheckerItem))
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second * time.Duration(c.interval))
|
time.Sleep(time.Second * time.Duration(c.interval))
|
||||||
|
if c.isStop {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -135,7 +153,7 @@ func (c *Checker) IsBlocked(address string) (blocked bool, failN, successN uint)
|
|||||||
func (c *Checker) domainIsInMap(address string, blockedMap bool) bool {
|
func (c *Checker) domainIsInMap(address string, blockedMap bool) bool {
|
||||||
u, err := url.Parse("http://" + address)
|
u, err := url.Parse("http://" + address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("blocked check , url parse err:%s", err)
|
c.log.Printf("blocked check , url parse err:%s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
domainSlice := strings.Split(u.Hostname(), ".")
|
domainSlice := strings.Split(u.Hostname(), ".")
|
||||||
@ -155,22 +173,13 @@ func (c *Checker) domainIsInMap(address string, blockedMap bool) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (c *Checker) Add(address string, isHTTPS bool, method, URL string, data []byte) {
|
func (c *Checker) Add(address string) {
|
||||||
if c.domainIsInMap(address, false) || c.domainIsInMap(address, true) {
|
if c.domainIsInMap(address, false) || c.domainIsInMap(address, true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !isHTTPS && strings.ToLower(method) != "get" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var item CheckerItem
|
var item CheckerItem
|
||||||
u := strings.Split(address, ":")
|
|
||||||
item = CheckerItem{
|
item = CheckerItem{
|
||||||
URL: URL,
|
Host: address,
|
||||||
Domain: u[0],
|
|
||||||
Host: address,
|
|
||||||
Data: data,
|
|
||||||
IsHTTPS: isHTTPS,
|
|
||||||
Method: method,
|
|
||||||
}
|
}
|
||||||
c.data.SetIfAbsent(item.Host, item)
|
c.data.SetIfAbsent(item.Host, item)
|
||||||
}
|
}
|
||||||
@ -181,11 +190,15 @@ type BasicAuth struct {
|
|||||||
authOkCode int
|
authOkCode int
|
||||||
authTimeout int
|
authTimeout int
|
||||||
authRetry int
|
authRetry int
|
||||||
|
dns *DomainResolver
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBasicAuth() BasicAuth {
|
func NewBasicAuth(dns *DomainResolver, log *logger.Logger) BasicAuth {
|
||||||
return BasicAuth{
|
return BasicAuth{
|
||||||
data: NewConcurrentMap(),
|
data: NewConcurrentMap(),
|
||||||
|
dns: dns,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (ba *BasicAuth) SetAuthURL(URL string, code, timeout, retry int) {
|
func (ba *BasicAuth) SetAuthURL(URL string, code, timeout, retry int) {
|
||||||
@ -238,7 +251,7 @@ func (ba *BasicAuth) Check(userpass string, ip, target string) (ok bool) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
log.Printf("%s", err)
|
ba.log.Printf("%s", err)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -249,18 +262,27 @@ func (ba *BasicAuth) checkFromURL(userpass, ip, target string) (err error) {
|
|||||||
if len(u) != 2 {
|
if len(u) != 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
URL := ba.authURL
|
URL := ba.authURL
|
||||||
if strings.Contains(URL, "?") {
|
if strings.Contains(URL, "?") {
|
||||||
URL += "&"
|
URL += "&"
|
||||||
} else {
|
} else {
|
||||||
URL += "?"
|
URL += "?"
|
||||||
}
|
}
|
||||||
URL += fmt.Sprintf("user=%s&pass=%s&ip=%s&target=%s", u[0], u[1], ip, target)
|
URL += fmt.Sprintf("user=%s&pass=%s&ip=%s&target=%s", u[0], u[1], ip, url.QueryEscape(target))
|
||||||
|
getURL := URL
|
||||||
|
var domain string
|
||||||
|
if ba.dns != nil {
|
||||||
|
_url, _ := url.Parse(ba.authURL)
|
||||||
|
domain = _url.Host
|
||||||
|
domainIP := ba.dns.MustResolve(domain)
|
||||||
|
getURL = strings.Replace(URL, domain, domainIP, 1)
|
||||||
|
}
|
||||||
var code int
|
var code int
|
||||||
var tryCount = 0
|
var tryCount = 0
|
||||||
var body []byte
|
var body []byte
|
||||||
for tryCount <= ba.authRetry {
|
for tryCount <= ba.authRetry {
|
||||||
body, code, err = HttpGet(URL, ba.authTimeout)
|
body, code, err = HttpGet(getURL, ba.authTimeout, domain)
|
||||||
if err == nil && code == ba.authOkCode {
|
if err == nil && code == ba.authOkCode {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@ -271,10 +293,14 @@ func (ba *BasicAuth) checkFromURL(userpass, ip, target string) (err error) {
|
|||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("token error")
|
err = fmt.Errorf("token error")
|
||||||
}
|
}
|
||||||
err = fmt.Errorf("auth fail from url %s,resonse code: %d, except: %d , %s , %s", URL, code, ba.authOkCode, ip, string(body))
|
b := string(body)
|
||||||
|
if len(b) > 50 {
|
||||||
|
b = b[:50]
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("auth fail from url %s,resonse code: %d, except: %d , %s , %s", URL, code, ba.authOkCode, ip, b)
|
||||||
}
|
}
|
||||||
if err != nil && tryCount < ba.authRetry {
|
if err != nil && tryCount < ba.authRetry {
|
||||||
log.Print(err)
|
ba.log.Print(err)
|
||||||
time.Sleep(time.Second * 2)
|
time.Sleep(time.Second * 2)
|
||||||
}
|
}
|
||||||
tryCount++
|
tryCount++
|
||||||
@ -300,30 +326,48 @@ type HTTPRequest struct {
|
|||||||
hostOrURL string
|
hostOrURL string
|
||||||
isBasicAuth bool
|
isBasicAuth bool
|
||||||
basicAuth *BasicAuth
|
basicAuth *BasicAuth
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *BasicAuth) (req HTTPRequest, err error) {
|
func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *BasicAuth, log *logger.Logger, header ...[]byte) (req HTTPRequest, err error) {
|
||||||
buf := make([]byte, bufSize)
|
buf := make([]byte, bufSize)
|
||||||
len := 0
|
n := 0
|
||||||
req = HTTPRequest{
|
req = HTTPRequest{
|
||||||
conn: inConn,
|
conn: inConn,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
len, err = (*inConn).Read(buf[:])
|
if header != nil && len(header) == 1 && len(header[0]) > 1 {
|
||||||
if err != nil {
|
buf = header[0]
|
||||||
if err != io.EOF {
|
n = len(header[0])
|
||||||
err = fmt.Errorf("http decoder read err:%s", err)
|
} else {
|
||||||
|
n, err = (*inConn).Read(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
err = fmt.Errorf("http decoder read err:%s", err)
|
||||||
|
}
|
||||||
|
CloseConn(inConn)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
CloseConn(inConn)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
req.HeadBuf = buf[:len]
|
|
||||||
index := bytes.IndexByte(req.HeadBuf, '\n')
|
req.HeadBuf = buf[:n]
|
||||||
if index == -1 {
|
//fmt.Println(string(req.HeadBuf))
|
||||||
err = fmt.Errorf("http decoder data line err:%s", SubStr(string(req.HeadBuf), 0, 50))
|
//try sni
|
||||||
CloseConn(inConn)
|
serverName, err0 := sni.ServerNameFromBytes(req.HeadBuf)
|
||||||
return
|
if err0 == nil {
|
||||||
|
//sni success
|
||||||
|
req.Method = "SNI"
|
||||||
|
req.hostOrURL = "https://" + serverName + ":443"
|
||||||
|
} else {
|
||||||
|
//sni fail , try http
|
||||||
|
index := bytes.IndexByte(req.HeadBuf, '\n')
|
||||||
|
if index == -1 {
|
||||||
|
err = fmt.Errorf("http decoder data line err:%s", SubStr(string(req.HeadBuf), 0, 50))
|
||||||
|
CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Sscanf(string(req.HeadBuf[:index]), "%s%s", &req.Method, &req.hostOrURL)
|
||||||
}
|
}
|
||||||
fmt.Sscanf(string(req.HeadBuf[:index]), "%s%s", &req.Method, &req.hostOrURL)
|
|
||||||
if req.Method == "" || req.hostOrURL == "" {
|
if req.Method == "" || req.hostOrURL == "" {
|
||||||
err = fmt.Errorf("http decoder data err:%s", SubStr(string(req.HeadBuf), 0, 50))
|
err = fmt.Errorf("http decoder data err:%s", SubStr(string(req.HeadBuf), 0, 50))
|
||||||
CloseConn(inConn)
|
CloseConn(inConn)
|
||||||
@ -348,22 +392,25 @@ func (req *HTTPRequest) HTTP() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.URL, err = req.getHTTPURL()
|
req.URL = req.getHTTPURL()
|
||||||
if err == nil {
|
var u *url.URL
|
||||||
var u *url.URL
|
u, err = url.Parse(req.URL)
|
||||||
u, err = url.Parse(req.URL)
|
if err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Host = u.Host
|
|
||||||
req.addPortIfNot()
|
|
||||||
}
|
}
|
||||||
|
req.Host = u.Host
|
||||||
|
req.addPortIfNot()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (req *HTTPRequest) HTTPS() (err error) {
|
func (req *HTTPRequest) HTTPS() (err error) {
|
||||||
|
if req.isBasicAuth {
|
||||||
|
err = req.BasicAuth()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
req.Host = req.hostOrURL
|
req.Host = req.hostOrURL
|
||||||
req.addPortIfNot()
|
req.addPortIfNot()
|
||||||
//_, err = fmt.Fprint(*req.conn, "HTTP/1.1 200 Connection established\r\n\r\n")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (req *HTTPRequest) HTTPSReply() (err error) {
|
func (req *HTTPRequest) HTTPSReply() (err error) {
|
||||||
@ -374,24 +421,18 @@ func (req *HTTPRequest) IsHTTPS() bool {
|
|||||||
return req.Method == "CONNECT"
|
return req.Method == "CONNECT"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req *HTTPRequest) BasicAuth() (err error) {
|
func (req *HTTPRequest) GetAuthDataStr() (basicInfo string, err error) {
|
||||||
|
// log.Printf("request :%s", string(req.HeadBuf))
|
||||||
|
authorization := req.getHeader("Proxy-Authorization")
|
||||||
|
|
||||||
//log.Printf("request :%s", string(b[:n]))
|
authorization = strings.Trim(authorization, " \r\n\t")
|
||||||
authorization, err := req.getHeader("Authorization")
|
if authorization == "" {
|
||||||
if err != nil {
|
fmt.Fprintf((*req.conn), "HTTP/1.1 %s Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"\"\r\n\r\nProxy Authentication Required", "407")
|
||||||
fmt.Fprint((*req.conn), "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized")
|
|
||||||
CloseConn(req.conn)
|
CloseConn(req.conn)
|
||||||
|
err = errors.New("require auth header data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if authorization == "" {
|
//log.Printf("Authorization:%authorization = req.getHeader("Authorization")
|
||||||
authorization, err = req.getHeader("Proxy-Authorization")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint((*req.conn), "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized")
|
|
||||||
CloseConn(req.conn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//log.Printf("Authorization:%s", authorization)
|
|
||||||
basic := strings.Fields(authorization)
|
basic := strings.Fields(authorization)
|
||||||
if len(basic) != 2 {
|
if len(basic) != 2 {
|
||||||
err = fmt.Errorf("authorization data error,ERR:%s", authorization)
|
err = fmt.Errorf("authorization data error,ERR:%s", authorization)
|
||||||
@ -404,43 +445,51 @@ func (req *HTTPRequest) BasicAuth() (err error) {
|
|||||||
CloseConn(req.conn)
|
CloseConn(req.conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
basicInfo = string(user)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (req *HTTPRequest) BasicAuth() (err error) {
|
||||||
addr := strings.Split((*req.conn).RemoteAddr().String(), ":")
|
addr := strings.Split((*req.conn).RemoteAddr().String(), ":")
|
||||||
URL := ""
|
URL := ""
|
||||||
if req.IsHTTPS() {
|
if req.IsHTTPS() {
|
||||||
URL = "https://" + req.Host
|
URL = "https://" + req.Host
|
||||||
} else {
|
} else {
|
||||||
URL, _ = req.getHTTPURL()
|
URL = req.getHTTPURL()
|
||||||
|
}
|
||||||
|
user, err := req.GetAuthDataStr()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
authOk := (*req.basicAuth).Check(string(user), addr[0], URL)
|
authOk := (*req.basicAuth).Check(string(user), addr[0], URL)
|
||||||
//log.Printf("auth %s,%v", string(user), authOk)
|
//log.Printf("auth %s,%v", string(user), authOk)
|
||||||
if !authOk {
|
if !authOk {
|
||||||
fmt.Fprint((*req.conn), "HTTP/1.1 401 Unauthorized\r\n\r\nUnauthorized")
|
fmt.Fprintf((*req.conn), "HTTP/1.1 %s Proxy Authentication Required\r\n\r\nProxy Authentication Required", "407")
|
||||||
CloseConn(req.conn)
|
CloseConn(req.conn)
|
||||||
err = fmt.Errorf("basic auth fail")
|
err = fmt.Errorf("basic auth fail")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (req *HTTPRequest) getHTTPURL() (URL string, err error) {
|
func (req *HTTPRequest) getHTTPURL() (URL string) {
|
||||||
if !strings.HasPrefix(req.hostOrURL, "/") {
|
if !strings.HasPrefix(req.hostOrURL, "/") {
|
||||||
return req.hostOrURL, nil
|
return req.hostOrURL
|
||||||
}
|
}
|
||||||
_host, err := req.getHeader("host")
|
_host := req.getHeader("host")
|
||||||
if err != nil {
|
if _host == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
URL = fmt.Sprintf("http://%s%s", _host, req.hostOrURL)
|
URL = fmt.Sprintf("http://%s%s", _host, req.hostOrURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (req *HTTPRequest) getHeader(key string) (val string, err error) {
|
func (req *HTTPRequest) getHeader(key string) (val string) {
|
||||||
key = strings.ToUpper(key)
|
key = strings.ToUpper(key)
|
||||||
lines := strings.Split(string(req.HeadBuf), "\r\n")
|
lines := strings.Split(string(req.HeadBuf), "\r\n")
|
||||||
//log.Println(lines)
|
//log.Println(lines)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2)
|
hline := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2)
|
||||||
if len(line) == 2 {
|
if len(hline) == 2 {
|
||||||
k := strings.ToUpper(strings.Trim(line[0], " "))
|
k := strings.ToUpper(strings.Trim(hline[0], " "))
|
||||||
v := strings.Trim(line[1], " ")
|
v := strings.Trim(hline[1], " ")
|
||||||
if key == k {
|
if key == k {
|
||||||
val = v
|
val = v
|
||||||
return
|
return
|
||||||
@ -464,252 +513,55 @@ func (req *HTTPRequest) addPortIfNot() (newHost string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutPool struct {
|
type OutConn struct {
|
||||||
Pool ConnPool
|
dur int
|
||||||
dur int
|
typ string
|
||||||
typ string
|
certBytes []byte
|
||||||
certBytes []byte
|
keyBytes []byte
|
||||||
keyBytes []byte
|
caCertBytes []byte
|
||||||
kcpMethod string
|
kcp kcpcfg.KCPConfigArgs
|
||||||
kcpKey string
|
address string
|
||||||
address string
|
timeout int
|
||||||
timeout int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOutPool(dur int, typ, kcpMethod, kcpKey string, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) {
|
func NewOutConn(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int) (op OutConn) {
|
||||||
op = OutPool{
|
return OutConn{
|
||||||
dur: dur,
|
dur: dur,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
certBytes: certBytes,
|
certBytes: certBytes,
|
||||||
keyBytes: keyBytes,
|
keyBytes: keyBytes,
|
||||||
kcpMethod: kcpMethod,
|
caCertBytes: caCertBytes,
|
||||||
kcpKey: kcpKey,
|
kcp: kcp,
|
||||||
address: address,
|
address: address,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
op.Pool, err = NewConnPool(poolConfig{
|
|
||||||
IsActive: func(conn interface{}) bool { return true },
|
|
||||||
Release: func(conn interface{}) {
|
|
||||||
if conn != nil {
|
|
||||||
conn.(net.Conn).SetDeadline(time.Now().Add(time.Millisecond))
|
|
||||||
conn.(net.Conn).Close()
|
|
||||||
// log.Println("conn released")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
InitialCap: InitialCap,
|
|
||||||
MaxCap: MaxCap,
|
|
||||||
Factory: func() (conn interface{}, err error) {
|
|
||||||
conn, err = op.getConn()
|
|
||||||
return
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("init conn pool fail ,%s", err)
|
|
||||||
} else {
|
|
||||||
if InitialCap > 0 {
|
|
||||||
log.Printf("init conn pool success")
|
|
||||||
op.initPoolDeamon()
|
|
||||||
} else {
|
|
||||||
log.Printf("conn pool closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
func (op *OutPool) getConn() (conn interface{}, err error) {
|
func (op *OutConn) Get() (conn net.Conn, err error) {
|
||||||
if op.typ == "tls" {
|
if op.typ == "tls" {
|
||||||
var _conn tls.Conn
|
var _conn tls.Conn
|
||||||
_conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes)
|
_conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes, op.caCertBytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conn = net.Conn(&_conn)
|
conn = net.Conn(&_conn)
|
||||||
}
|
}
|
||||||
} else if op.typ == "kcp" {
|
} else if op.typ == "kcp" {
|
||||||
conn, err = ConnectKCPHost(op.address, op.kcpMethod, op.kcpKey)
|
conn, err = ConnectKCPHost(op.address, op.kcp)
|
||||||
} else {
|
} else {
|
||||||
conn, err = ConnectHost(op.address, op.timeout)
|
conn, err = ConnectHost(op.address, op.timeout)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *OutPool) initPoolDeamon() {
|
|
||||||
go func() {
|
|
||||||
if op.dur <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("pool deamon started")
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Second * time.Duration(op.dur))
|
|
||||||
conn, err := op.getConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("pool deamon err %s , release pool", err)
|
|
||||||
op.Pool.ReleaseAll()
|
|
||||||
} else {
|
|
||||||
conn.(net.Conn).SetDeadline(time.Now().Add(time.Millisecond))
|
|
||||||
conn.(net.Conn).Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
type HeartbeatData struct {
|
|
||||||
Data []byte
|
|
||||||
N int
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
type HeartbeatReadWriter struct {
|
|
||||||
conn *net.Conn
|
|
||||||
// rchn chan HeartbeatData
|
|
||||||
l *sync.Mutex
|
|
||||||
dur int
|
|
||||||
errHandler func(err error, hb *HeartbeatReadWriter)
|
|
||||||
once *sync.Once
|
|
||||||
datachn chan byte
|
|
||||||
// rbuf bytes.Buffer
|
|
||||||
// signal chan bool
|
|
||||||
rerrchn chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHeartbeatReadWriter(conn *net.Conn, dur int, fn func(err error, hb *HeartbeatReadWriter)) (hrw HeartbeatReadWriter) {
|
|
||||||
hrw = HeartbeatReadWriter{
|
|
||||||
conn: conn,
|
|
||||||
l: &sync.Mutex{},
|
|
||||||
dur: dur,
|
|
||||||
// rchn: make(chan HeartbeatData, 10000),
|
|
||||||
// signal: make(chan bool, 1),
|
|
||||||
errHandler: fn,
|
|
||||||
datachn: make(chan byte, 4*1024),
|
|
||||||
once: &sync.Once{},
|
|
||||||
rerrchn: make(chan error, 1),
|
|
||||||
// rbuf: bytes.Buffer{},
|
|
||||||
}
|
|
||||||
hrw.heartbeat()
|
|
||||||
hrw.reader()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *HeartbeatReadWriter) Close() {
|
|
||||||
CloseConn(rw.conn)
|
|
||||||
}
|
|
||||||
func (rw *HeartbeatReadWriter) reader() {
|
|
||||||
go func() {
|
|
||||||
//log.Printf("heartbeat read started")
|
|
||||||
for {
|
|
||||||
n, data, err := rw.read()
|
|
||||||
if n == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//log.Printf("n:%d , data:%s ,err:%s", n, string(data), err)
|
|
||||||
if err == nil {
|
|
||||||
//fmt.Printf("write data %s\n", string(data))
|
|
||||||
for _, b := range data {
|
|
||||||
rw.datachn <- b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
//log.Printf("heartbeat reader err: %s", err)
|
|
||||||
select {
|
|
||||||
case rw.rerrchn <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
rw.once.Do(func() {
|
|
||||||
rw.errHandler(err, rw)
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//log.Printf("heartbeat read exited")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
func (rw *HeartbeatReadWriter) read() (n int, data []byte, err error) {
|
|
||||||
var typ uint8
|
|
||||||
err = binary.Read((*rw.conn), binary.LittleEndian, &typ)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if typ == 0 {
|
|
||||||
// log.Printf("heartbeat revecived")
|
|
||||||
n = -1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var dataLength uint32
|
|
||||||
binary.Read((*rw.conn), binary.LittleEndian, &dataLength)
|
|
||||||
_data := make([]byte, dataLength)
|
|
||||||
// log.Printf("dataLength:%d , data:%s", dataLength, string(data))
|
|
||||||
n, err = (*rw.conn).Read(_data)
|
|
||||||
//log.Printf("n:%d , data:%s ,err:%s", n, string(data), err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if uint32(n) != dataLength {
|
|
||||||
err = fmt.Errorf("read short data body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = _data[:n]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func (rw *HeartbeatReadWriter) heartbeat() {
|
|
||||||
go func() {
|
|
||||||
//log.Printf("heartbeat started")
|
|
||||||
for {
|
|
||||||
if rw.conn == nil || *rw.conn == nil {
|
|
||||||
//log.Printf("heartbeat err: conn nil")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
rw.l.Lock()
|
|
||||||
_, err := (*rw.conn).Write([]byte{0})
|
|
||||||
rw.l.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
//log.Printf("heartbeat err: %s", err)
|
|
||||||
rw.once.Do(func() {
|
|
||||||
rw.errHandler(err, rw)
|
|
||||||
})
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
// log.Printf("heartbeat send ok")
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * time.Duration(rw.dur))
|
|
||||||
}
|
|
||||||
//log.Printf("heartbeat exited")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
func (rw *HeartbeatReadWriter) Read(p []byte) (n int, err error) {
|
|
||||||
data := make([]byte, cap(p))
|
|
||||||
for i := 0; i < cap(p); i++ {
|
|
||||||
data[i] = <-rw.datachn
|
|
||||||
n++
|
|
||||||
//fmt.Printf("read %d %v\n", i, data[:n])
|
|
||||||
if len(rw.datachn) == 0 {
|
|
||||||
n = i + 1
|
|
||||||
copy(p, data[:n])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func (rw *HeartbeatReadWriter) Write(p []byte) (n int, err error) {
|
|
||||||
defer rw.l.Unlock()
|
|
||||||
rw.l.Lock()
|
|
||||||
pkg := new(bytes.Buffer)
|
|
||||||
binary.Write(pkg, binary.LittleEndian, uint8(1))
|
|
||||||
binary.Write(pkg, binary.LittleEndian, uint32(len(p)))
|
|
||||||
binary.Write(pkg, binary.LittleEndian, p)
|
|
||||||
bs := pkg.Bytes()
|
|
||||||
n, err = (*rw.conn).Write(bs)
|
|
||||||
if err == nil {
|
|
||||||
n = len(p)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnManager struct {
|
type ConnManager struct {
|
||||||
pool ConcurrentMap
|
pool ConcurrentMap
|
||||||
l *sync.Mutex
|
l *sync.Mutex
|
||||||
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnManager() ConnManager {
|
func NewConnManager(log *logger.Logger) ConnManager {
|
||||||
cm := ConnManager{
|
cm := ConnManager{
|
||||||
pool: NewConcurrentMap(),
|
pool: NewConcurrentMap(),
|
||||||
l: &sync.Mutex{},
|
l: &sync.Mutex{},
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
@ -726,7 +578,7 @@ func (cm *ConnManager) Add(key, ID string, conn *net.Conn) {
|
|||||||
(*v.(*net.Conn)).Close()
|
(*v.(*net.Conn)).Close()
|
||||||
}
|
}
|
||||||
conns.Set(ID, conn)
|
conns.Set(ID, conn)
|
||||||
log.Printf("%s conn added", key)
|
cm.log.Printf("%s conn added", key)
|
||||||
return conns
|
return conns
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -737,7 +589,7 @@ func (cm *ConnManager) Remove(key string) {
|
|||||||
conns.IterCb(func(key string, v interface{}) {
|
conns.IterCb(func(key string, v interface{}) {
|
||||||
CloseConn(v.(*net.Conn))
|
CloseConn(v.(*net.Conn))
|
||||||
})
|
})
|
||||||
log.Printf("%s conns closed", key)
|
cm.log.Printf("%s conns closed", key)
|
||||||
}
|
}
|
||||||
cm.pool.Remove(key)
|
cm.pool.Remove(key)
|
||||||
}
|
}
|
||||||
@ -752,7 +604,7 @@ func (cm *ConnManager) RemoveOne(key string, ID string) {
|
|||||||
(*v.(*net.Conn)).Close()
|
(*v.(*net.Conn)).Close()
|
||||||
conns.Remove(ID)
|
conns.Remove(ID)
|
||||||
cm.pool.Set(key, conns)
|
cm.pool.Set(key, conns)
|
||||||
log.Printf("%s %s conn closed", key, ID)
|
cm.log.Printf("%s %s conn closed", key, ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -761,3 +613,223 @@ func (cm *ConnManager) RemoveAll() {
|
|||||||
cm.Remove(k)
|
cm.Remove(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientKeyRouter struct {
|
||||||
|
keyChan chan string
|
||||||
|
ctrl *ConcurrentMap
|
||||||
|
lock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientKeyRouter(ctrl *ConcurrentMap, size int) ClientKeyRouter {
|
||||||
|
return ClientKeyRouter{
|
||||||
|
keyChan: make(chan string, size),
|
||||||
|
ctrl: ctrl,
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (c *ClientKeyRouter) GetKey() string {
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
c.lock.Lock()
|
||||||
|
if len(c.keyChan) == 0 {
|
||||||
|
EXIT:
|
||||||
|
for _, k := range c.ctrl.Keys() {
|
||||||
|
select {
|
||||||
|
case c.keyChan <- k:
|
||||||
|
default:
|
||||||
|
goto EXIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if len(c.keyChan) == 0 {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case key := <-c.keyChan:
|
||||||
|
if c.ctrl.Has(key) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainResolver struct {
|
||||||
|
ttl int
|
||||||
|
dnsAddrress string
|
||||||
|
data ConcurrentMap
|
||||||
|
log *logger.Logger
|
||||||
|
}
|
||||||
|
type DomainResolverItem struct {
|
||||||
|
ip string
|
||||||
|
domain string
|
||||||
|
expiredAt int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDomainResolver(dnsAddrress string, ttl int, log *logger.Logger) DomainResolver {
|
||||||
|
return DomainResolver{
|
||||||
|
ttl: ttl,
|
||||||
|
dnsAddrress: dnsAddrress,
|
||||||
|
data: NewConcurrentMap(),
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (a *DomainResolver) MustResolve(address string) (ip string) {
|
||||||
|
ip, _ = a.Resolve(address)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (a *DomainResolver) Resolve(address string) (ip string, err error) {
|
||||||
|
domain := address
|
||||||
|
port := ""
|
||||||
|
fromCache := "false"
|
||||||
|
defer func() {
|
||||||
|
if port != "" {
|
||||||
|
ip = net.JoinHostPort(ip, port)
|
||||||
|
}
|
||||||
|
a.log.Printf("dns:%s->%s,cache:%s", address, ip, fromCache)
|
||||||
|
//a.PrintData()
|
||||||
|
}()
|
||||||
|
if strings.Contains(domain, ":") {
|
||||||
|
domain, port, err = net.SplitHostPort(domain)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if net.ParseIP(domain) != nil {
|
||||||
|
ip = domain
|
||||||
|
fromCache = "ip ignore"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item, ok := a.data.Get(domain)
|
||||||
|
if ok {
|
||||||
|
//log.Println("find ", domain)
|
||||||
|
if (*item.(*DomainResolverItem)).expiredAt > time.Now().Unix() {
|
||||||
|
ip = (*item.(*DomainResolverItem)).ip
|
||||||
|
fromCache = "true"
|
||||||
|
//log.Println("from cache ", domain)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item = &DomainResolverItem{
|
||||||
|
domain: domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
c := new(dns.Client)
|
||||||
|
c.DialTimeout = time.Millisecond * 5000
|
||||||
|
c.ReadTimeout = time.Millisecond * 5000
|
||||||
|
c.WriteTimeout = time.Millisecond * 5000
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeA)
|
||||||
|
m.RecursionDesired = true
|
||||||
|
r, _, err := c.Exchange(m, a.dnsAddrress)
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
err = fmt.Errorf(" *** invalid answer name %s after A query for %s", domain, a.dnsAddrress)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, answer := range r.Answer {
|
||||||
|
if answer.Header().Rrtype == dns.TypeA {
|
||||||
|
info := strings.Fields(answer.String())
|
||||||
|
if len(info) >= 5 {
|
||||||
|
ip = info[4]
|
||||||
|
_item := item.(*DomainResolverItem)
|
||||||
|
(*_item).expiredAt = time.Now().Unix() + int64(a.ttl)
|
||||||
|
(*_item).ip = ip
|
||||||
|
a.data.Set(domain, item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (a *DomainResolver) PrintData() {
|
||||||
|
for k, item := range a.data.Items() {
|
||||||
|
d := item.(*DomainResolverItem)
|
||||||
|
a.log.Printf("%s:ip[%s],domain[%s],expired at[%d]\n", k, (*d).ip, (*d).domain, (*d).expiredAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func NewCompStream(conn net.Conn) *CompStream {
|
||||||
|
c := new(CompStream)
|
||||||
|
c.conn = conn
|
||||||
|
c.w = snappy.NewBufferedWriter(conn)
|
||||||
|
c.r = snappy.NewReader(conn)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
func NewCompConn(conn net.Conn) net.Conn {
|
||||||
|
c := CompStream{}
|
||||||
|
c.conn = conn
|
||||||
|
c.w = snappy.NewBufferedWriter(conn)
|
||||||
|
c.r = snappy.NewReader(conn)
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompStream struct {
|
||||||
|
net.Conn
|
||||||
|
conn net.Conn
|
||||||
|
w *snappy.Writer
|
||||||
|
r *snappy.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompStream) Read(p []byte) (n int, err error) {
|
||||||
|
return c.r.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompStream) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.w.Write(p)
|
||||||
|
err = c.w.Flush()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompStream) Close() error {
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
func (c *CompStream) LocalAddr() net.Addr {
|
||||||
|
return c.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
func (c *CompStream) RemoteAddr() net.Addr {
|
||||||
|
return c.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
func (c *CompStream) SetDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
func (c *CompStream) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
func (c *CompStream) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BufferedConn struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
net.Conn // So that most methods are embedded
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBufferedConn(c net.Conn) BufferedConn {
|
||||||
|
return BufferedConn{bufio.NewReader(c), c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBufferedConnSize(c net.Conn, n int) BufferedConn {
|
||||||
|
return BufferedConn{bufio.NewReaderSize(c, n), c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BufferedConn) Peek(n int) ([]byte, error) {
|
||||||
|
return b.r.Peek(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BufferedConn) Read(p []byte) (int, error) {
|
||||||
|
return b.r.Read(p)
|
||||||
|
}
|
||||||
|
func (b BufferedConn) ReadByte() (byte, error) {
|
||||||
|
return b.r.ReadByte()
|
||||||
|
}
|
||||||
|
func (b BufferedConn) UnreadByte() error {
|
||||||
|
return b.r.UnreadByte()
|
||||||
|
}
|
||||||
|
func (b BufferedConn) Buffered() int {
|
||||||
|
return b.r.Buffered()
|
||||||
|
}
|
||||||
|
|||||||
122
vendor/github.com/Yawning/chacha20/LICENSE
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
|
|
||||||
14
vendor/github.com/Yawning/chacha20/README.md
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
### chacha20 - ChaCha20
|
||||||
|
#### Yawning Angel (yawning at schwanenlied dot me)
|
||||||
|
|
||||||
|
Yet another Go ChaCha20 implementation. Everything else I found was slow,
|
||||||
|
didn't support all the variants I need to use, or relied on cgo to go fast.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* 20 round, 256 bit key only. Everything else is pointless and stupid.
|
||||||
|
* IETF 96 bit nonce variant.
|
||||||
|
* XChaCha 24 byte nonce variant.
|
||||||
|
* SSE2 and AVX2 support on amd64 targets.
|
||||||
|
* Incremental encrypt/decrypt support, unlike golang.org/x/crypto/salsa20.
|
||||||
|
|
||||||
273
vendor/github.com/Yawning/chacha20/chacha20.go
generated
vendored
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// chacha20.go - A ChaCha stream cipher implementation.
|
||||||
|
//
|
||||||
|
// To the extent possible under law, Yawning Angel has waived all copyright
|
||||||
|
// and related or neighboring rights to chacha20, using the Creative
|
||||||
|
// Commons "CC0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// KeySize is the ChaCha20 key size in bytes.
|
||||||
|
KeySize = 32
|
||||||
|
|
||||||
|
// NonceSize is the ChaCha20 nonce size in bytes.
|
||||||
|
NonceSize = 8
|
||||||
|
|
||||||
|
// INonceSize is the IETF ChaCha20 nonce size in bytes.
|
||||||
|
INonceSize = 12
|
||||||
|
|
||||||
|
// XNonceSize is the XChaCha20 nonce size in bytes.
|
||||||
|
XNonceSize = 24
|
||||||
|
|
||||||
|
// HNonceSize is the HChaCha20 nonce size in bytes.
|
||||||
|
HNonceSize = 16
|
||||||
|
|
||||||
|
// BlockSize is the ChaCha20 block size in bytes.
|
||||||
|
BlockSize = 64
|
||||||
|
|
||||||
|
stateSize = 16
|
||||||
|
chachaRounds = 20
|
||||||
|
|
||||||
|
// The constant "expand 32-byte k" as little endian uint32s.
|
||||||
|
sigma0 = uint32(0x61707865)
|
||||||
|
sigma1 = uint32(0x3320646e)
|
||||||
|
sigma2 = uint32(0x79622d32)
|
||||||
|
sigma3 = uint32(0x6b206574)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidKey is the error returned when the key is invalid.
|
||||||
|
ErrInvalidKey = errors.New("key length must be KeySize bytes")
|
||||||
|
|
||||||
|
// ErrInvalidNonce is the error returned when the nonce is invalid.
|
||||||
|
ErrInvalidNonce = errors.New("nonce length must be NonceSize/INonceSize/XNonceSize bytes")
|
||||||
|
|
||||||
|
// ErrInvalidCounter is the error returned when the counter is invalid.
|
||||||
|
ErrInvalidCounter = errors.New("block counter is invalid (out of range)")
|
||||||
|
|
||||||
|
useUnsafe = false
|
||||||
|
usingVectors = false
|
||||||
|
blocksFn = blocksRef
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Cipher is an instance of ChaCha20/XChaCha20 using a particular key and
|
||||||
|
// nonce.
|
||||||
|
type Cipher struct {
|
||||||
|
state [stateSize]uint32
|
||||||
|
|
||||||
|
buf [BlockSize]byte
|
||||||
|
off int
|
||||||
|
ietf bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset zeros the key data so that it will no longer appear in the process's
|
||||||
|
// memory.
|
||||||
|
func (c *Cipher) Reset() {
|
||||||
|
for i := range c.state {
|
||||||
|
c.state[i] = 0
|
||||||
|
}
|
||||||
|
for i := range c.buf {
|
||||||
|
c.buf[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XORKeyStream sets dst to the result of XORing src with the key stream. Dst
|
||||||
|
// and src may be the same slice but otherwise should not overlap.
|
||||||
|
func (c *Cipher) XORKeyStream(dst, src []byte) {
|
||||||
|
if len(dst) < len(src) {
|
||||||
|
src = src[:len(dst)]
|
||||||
|
}
|
||||||
|
|
||||||
|
for remaining := len(src); remaining > 0; {
|
||||||
|
// Process multiple blocks at once.
|
||||||
|
if c.off == BlockSize {
|
||||||
|
nrBlocks := remaining / BlockSize
|
||||||
|
directBytes := nrBlocks * BlockSize
|
||||||
|
if nrBlocks > 0 {
|
||||||
|
blocksFn(&c.state, src, dst, nrBlocks, c.ietf)
|
||||||
|
remaining -= directBytes
|
||||||
|
if remaining == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst = dst[directBytes:]
|
||||||
|
src = src[directBytes:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a partial block, generate 1 block of keystream into
|
||||||
|
// the internal buffer.
|
||||||
|
blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
|
||||||
|
c.off = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process partial blocks from the buffered keystream.
|
||||||
|
toXor := BlockSize - c.off
|
||||||
|
if remaining < toXor {
|
||||||
|
toXor = remaining
|
||||||
|
}
|
||||||
|
if toXor > 0 {
|
||||||
|
for i, v := range src[:toXor] {
|
||||||
|
dst[i] = v ^ c.buf[c.off+i]
|
||||||
|
}
|
||||||
|
dst = dst[toXor:]
|
||||||
|
src = src[toXor:]
|
||||||
|
|
||||||
|
remaining -= toXor
|
||||||
|
c.off += toXor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyStream sets dst to the raw keystream.
|
||||||
|
func (c *Cipher) KeyStream(dst []byte) {
|
||||||
|
for remaining := len(dst); remaining > 0; {
|
||||||
|
// Process multiple blocks at once.
|
||||||
|
if c.off == BlockSize {
|
||||||
|
nrBlocks := remaining / BlockSize
|
||||||
|
directBytes := nrBlocks * BlockSize
|
||||||
|
if nrBlocks > 0 {
|
||||||
|
blocksFn(&c.state, nil, dst, nrBlocks, c.ietf)
|
||||||
|
remaining -= directBytes
|
||||||
|
if remaining == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst = dst[directBytes:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a partial block, generate 1 block of keystream into
|
||||||
|
// the internal buffer.
|
||||||
|
blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
|
||||||
|
c.off = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process partial blocks from the buffered keystream.
|
||||||
|
toCopy := BlockSize - c.off
|
||||||
|
if remaining < toCopy {
|
||||||
|
toCopy = remaining
|
||||||
|
}
|
||||||
|
if toCopy > 0 {
|
||||||
|
copy(dst[:toCopy], c.buf[c.off:c.off+toCopy])
|
||||||
|
dst = dst[toCopy:]
|
||||||
|
remaining -= toCopy
|
||||||
|
c.off += toCopy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReKey reinitializes the ChaCha20/XChaCha20 instance with the provided key
|
||||||
|
// and nonce.
|
||||||
|
func (c *Cipher) ReKey(key, nonce []byte) error {
|
||||||
|
if len(key) != KeySize {
|
||||||
|
return ErrInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(nonce) {
|
||||||
|
case NonceSize:
|
||||||
|
case INonceSize:
|
||||||
|
case XNonceSize:
|
||||||
|
var subkey [KeySize]byte
|
||||||
|
var subnonce [HNonceSize]byte
|
||||||
|
copy(subnonce[:], nonce[0:16])
|
||||||
|
HChaCha(key, &subnonce, &subkey)
|
||||||
|
key = subkey[:]
|
||||||
|
nonce = nonce[16:24]
|
||||||
|
defer func() {
|
||||||
|
for i := range subkey {
|
||||||
|
subkey[i] = 0
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
return ErrInvalidNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Reset()
|
||||||
|
c.state[0] = sigma0
|
||||||
|
c.state[1] = sigma1
|
||||||
|
c.state[2] = sigma2
|
||||||
|
c.state[3] = sigma3
|
||||||
|
c.state[4] = binary.LittleEndian.Uint32(key[0:4])
|
||||||
|
c.state[5] = binary.LittleEndian.Uint32(key[4:8])
|
||||||
|
c.state[6] = binary.LittleEndian.Uint32(key[8:12])
|
||||||
|
c.state[7] = binary.LittleEndian.Uint32(key[12:16])
|
||||||
|
c.state[8] = binary.LittleEndian.Uint32(key[16:20])
|
||||||
|
c.state[9] = binary.LittleEndian.Uint32(key[20:24])
|
||||||
|
c.state[10] = binary.LittleEndian.Uint32(key[24:28])
|
||||||
|
c.state[11] = binary.LittleEndian.Uint32(key[28:32])
|
||||||
|
c.state[12] = 0
|
||||||
|
if len(nonce) == INonceSize {
|
||||||
|
c.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
|
||||||
|
c.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
|
||||||
|
c.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
|
||||||
|
c.ietf = true
|
||||||
|
} else {
|
||||||
|
c.state[13] = 0
|
||||||
|
c.state[14] = binary.LittleEndian.Uint32(nonce[0:4])
|
||||||
|
c.state[15] = binary.LittleEndian.Uint32(nonce[4:8])
|
||||||
|
c.ietf = false
|
||||||
|
}
|
||||||
|
c.off = BlockSize
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek sets the block counter to a given offset.
|
||||||
|
func (c *Cipher) Seek(blockCounter uint64) error {
|
||||||
|
if c.ietf {
|
||||||
|
if blockCounter > math.MaxUint32 {
|
||||||
|
return ErrInvalidCounter
|
||||||
|
}
|
||||||
|
c.state[12] = uint32(blockCounter)
|
||||||
|
} else {
|
||||||
|
c.state[12] = uint32(blockCounter)
|
||||||
|
c.state[13] = uint32(blockCounter >> 32)
|
||||||
|
}
|
||||||
|
c.off = BlockSize
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCipher returns a new ChaCha20/XChaCha20 instance.
|
||||||
|
func NewCipher(key, nonce []byte) (*Cipher, error) {
|
||||||
|
c := new(Cipher)
|
||||||
|
if err := c.ReKey(key, nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HChaCha is the HChaCha20 hash function used to make XChaCha.
|
||||||
|
func HChaCha(key []byte, nonce *[HNonceSize]byte, out *[32]byte) {
|
||||||
|
var x [stateSize]uint32 // Last 4 slots unused, sigma hardcoded.
|
||||||
|
x[0] = binary.LittleEndian.Uint32(key[0:4])
|
||||||
|
x[1] = binary.LittleEndian.Uint32(key[4:8])
|
||||||
|
x[2] = binary.LittleEndian.Uint32(key[8:12])
|
||||||
|
x[3] = binary.LittleEndian.Uint32(key[12:16])
|
||||||
|
x[4] = binary.LittleEndian.Uint32(key[16:20])
|
||||||
|
x[5] = binary.LittleEndian.Uint32(key[20:24])
|
||||||
|
x[6] = binary.LittleEndian.Uint32(key[24:28])
|
||||||
|
x[7] = binary.LittleEndian.Uint32(key[28:32])
|
||||||
|
x[8] = binary.LittleEndian.Uint32(nonce[0:4])
|
||||||
|
x[9] = binary.LittleEndian.Uint32(nonce[4:8])
|
||||||
|
x[10] = binary.LittleEndian.Uint32(nonce[8:12])
|
||||||
|
x[11] = binary.LittleEndian.Uint32(nonce[12:16])
|
||||||
|
hChaChaRef(&x, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "386", "amd64":
|
||||||
|
// Abuse unsafe to skip calling binary.LittleEndian.PutUint32
|
||||||
|
// in the critical path. This is a big boost on systems that are
|
||||||
|
// little endian and not overly picky about alignment.
|
||||||
|
useUnsafe = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ cipher.Stream = (*Cipher)(nil)
|
||||||
95
vendor/github.com/Yawning/chacha20/chacha20_amd64.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// chacha20_amd64.go - AMD64 optimized chacha20.
|
||||||
|
//
|
||||||
|
// To the extent possible under law, Yawning Angel has waived all copyright
|
||||||
|
// and related or neighboring rights to chacha20, using the Creative
|
||||||
|
// Commons "CC0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var usingAVX2 = false
|
||||||
|
|
||||||
|
func blocksAmd64SSE2(x *uint32, inp, outp *byte, nrBlocks uint)
|
||||||
|
|
||||||
|
func blocksAmd64AVX2(x *uint32, inp, outp *byte, nrBlocks uint)
|
||||||
|
|
||||||
|
func cpuidAmd64(cpuidParams *uint32)
|
||||||
|
|
||||||
|
func xgetbv0Amd64(xcrVec *uint32)
|
||||||
|
|
||||||
|
func blocksAmd64(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
|
||||||
|
// Probably unneeded, but stating this explicitly simplifies the assembly.
|
||||||
|
if nrBlocks == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isIetf {
|
||||||
|
var totalBlocks uint64
|
||||||
|
totalBlocks = uint64(x[12]) + uint64(nrBlocks)
|
||||||
|
if totalBlocks > math.MaxUint32 {
|
||||||
|
panic("chacha20: Exceeded keystream per nonce limit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in == nil {
|
||||||
|
for i := range out {
|
||||||
|
out[i] = 0
|
||||||
|
}
|
||||||
|
in = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointless to call the AVX2 code for just a single block, since half of
|
||||||
|
// the output gets discarded...
|
||||||
|
if usingAVX2 && nrBlocks > 1 {
|
||||||
|
blocksAmd64AVX2(&x[0], &in[0], &out[0], uint(nrBlocks))
|
||||||
|
} else {
|
||||||
|
blocksAmd64SSE2(&x[0], &in[0], &out[0], uint(nrBlocks))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func supportsAVX2() bool {
|
||||||
|
// https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
|
||||||
|
const (
|
||||||
|
osXsaveBit = 1 << 27
|
||||||
|
avx2Bit = 1 << 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check to see if CPUID actually supports the leaf that indicates AVX2.
|
||||||
|
// CPUID.(EAX=0H, ECX=0H) >= 7
|
||||||
|
regs := [4]uint32{0x00}
|
||||||
|
cpuidAmd64(®s[0])
|
||||||
|
if regs[0] < 7 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if the OS knows how to save/restore XMM/YMM state.
|
||||||
|
// CPUID.(EAX=01H, ECX=0H):ECX.OSXSAVE[bit 27]==1
|
||||||
|
regs = [4]uint32{0x01}
|
||||||
|
cpuidAmd64(®s[0])
|
||||||
|
if regs[2]&osXsaveBit == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
xcrRegs := [2]uint32{}
|
||||||
|
xgetbv0Amd64(&xcrRegs[0])
|
||||||
|
if xcrRegs[0]&6 != 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for AVX2 support.
|
||||||
|
// CPUID.(EAX=07H, ECX=0H):EBX.AVX2[bit 5]==1
|
||||||
|
regs = [4]uint32{0x07}
|
||||||
|
cpuidAmd64(®s[0])
|
||||||
|
return regs[1]&avx2Bit != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
blocksFn = blocksAmd64
|
||||||
|
usingVectors = true
|
||||||
|
usingAVX2 = supportsAVX2()
|
||||||
|
}
|
||||||
1295
vendor/github.com/Yawning/chacha20/chacha20_amd64.py
generated
vendored
Normal file
1180
vendor/github.com/Yawning/chacha20/chacha20_amd64.s
generated
vendored
Normal file
394
vendor/github.com/Yawning/chacha20/chacha20_ref.go
generated
vendored
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
// chacha20_ref.go - Reference ChaCha20.
|
||||||
|
//
|
||||||
|
// To the extent possible under law, Yawning Angel has waived all copyright
|
||||||
|
// and related or neighboring rights to chacha20, using the Creative
|
||||||
|
// Commons "CC0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
// +build !go1.9
|
||||||
|
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func blocksRef(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
|
||||||
|
if isIetf {
|
||||||
|
var totalBlocks uint64
|
||||||
|
totalBlocks = uint64(x[12]) + uint64(nrBlocks)
|
||||||
|
if totalBlocks > math.MaxUint32 {
|
||||||
|
panic("chacha20: Exceeded keystream per nonce limit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This routine ignores x[0]...x[4] in favor the const values since it's
|
||||||
|
// ever so slightly faster.
|
||||||
|
|
||||||
|
for n := 0; n < nrBlocks; n++ {
|
||||||
|
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
|
||||||
|
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15]
|
||||||
|
|
||||||
|
for i := chachaRounds; i > 0; i -= 2 {
|
||||||
|
// quarterround(x, 0, 4, 8, 12)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = (x12 << 16) | (x12 >> 16)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = (x4 << 12) | (x4 >> 20)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = (x12 << 8) | (x12 >> 24)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = (x4 << 7) | (x4 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 5, 9, 13)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = (x13 << 16) | (x13 >> 16)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = (x5 << 12) | (x5 >> 20)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = (x13 << 8) | (x13 >> 24)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = (x5 << 7) | (x5 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 6, 10, 14)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = (x14 << 16) | (x14 >> 16)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = (x6 << 12) | (x6 >> 20)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = (x14 << 8) | (x14 >> 24)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = (x6 << 7) | (x6 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 7, 11, 15)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = (x15 << 16) | (x15 >> 16)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = (x7 << 12) | (x7 >> 20)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = (x15 << 8) | (x15 >> 24)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = (x7 << 7) | (x7 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 0, 5, 10, 15)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = (x15 << 16) | (x15 >> 16)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = (x5 << 12) | (x5 >> 20)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = (x15 << 8) | (x15 >> 24)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = (x5 << 7) | (x5 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 6, 11, 12)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = (x12 << 16) | (x12 >> 16)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = (x6 << 12) | (x6 >> 20)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = (x12 << 8) | (x12 >> 24)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = (x6 << 7) | (x6 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 7, 8, 13)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = (x13 << 16) | (x13 >> 16)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = (x7 << 12) | (x7 >> 20)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = (x13 << 8) | (x13 >> 24)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = (x7 << 7) | (x7 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 4, 9, 14)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = (x14 << 16) | (x14 >> 16)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = (x4 << 12) | (x4 >> 20)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = (x14 << 8) | (x14 >> 24)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = (x4 << 7) | (x4 >> 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On amd64 at least, this is a rather big boost.
|
||||||
|
if useUnsafe {
|
||||||
|
if in != nil {
|
||||||
|
inArr := (*[16]uint32)(unsafe.Pointer(&in[n*BlockSize]))
|
||||||
|
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
|
||||||
|
outArr[0] = inArr[0] ^ (x0 + sigma0)
|
||||||
|
outArr[1] = inArr[1] ^ (x1 + sigma1)
|
||||||
|
outArr[2] = inArr[2] ^ (x2 + sigma2)
|
||||||
|
outArr[3] = inArr[3] ^ (x3 + sigma3)
|
||||||
|
outArr[4] = inArr[4] ^ (x4 + x[4])
|
||||||
|
outArr[5] = inArr[5] ^ (x5 + x[5])
|
||||||
|
outArr[6] = inArr[6] ^ (x6 + x[6])
|
||||||
|
outArr[7] = inArr[7] ^ (x7 + x[7])
|
||||||
|
outArr[8] = inArr[8] ^ (x8 + x[8])
|
||||||
|
outArr[9] = inArr[9] ^ (x9 + x[9])
|
||||||
|
outArr[10] = inArr[10] ^ (x10 + x[10])
|
||||||
|
outArr[11] = inArr[11] ^ (x11 + x[11])
|
||||||
|
outArr[12] = inArr[12] ^ (x12 + x[12])
|
||||||
|
outArr[13] = inArr[13] ^ (x13 + x[13])
|
||||||
|
outArr[14] = inArr[14] ^ (x14 + x[14])
|
||||||
|
outArr[15] = inArr[15] ^ (x15 + x[15])
|
||||||
|
} else {
|
||||||
|
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
|
||||||
|
outArr[0] = x0 + sigma0
|
||||||
|
outArr[1] = x1 + sigma1
|
||||||
|
outArr[2] = x2 + sigma2
|
||||||
|
outArr[3] = x3 + sigma3
|
||||||
|
outArr[4] = x4 + x[4]
|
||||||
|
outArr[5] = x5 + x[5]
|
||||||
|
outArr[6] = x6 + x[6]
|
||||||
|
outArr[7] = x7 + x[7]
|
||||||
|
outArr[8] = x8 + x[8]
|
||||||
|
outArr[9] = x9 + x[9]
|
||||||
|
outArr[10] = x10 + x[10]
|
||||||
|
outArr[11] = x11 + x[11]
|
||||||
|
outArr[12] = x12 + x[12]
|
||||||
|
outArr[13] = x13 + x[13]
|
||||||
|
outArr[14] = x14 + x[14]
|
||||||
|
outArr[15] = x15 + x[15]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Slow path, either the architecture cares about alignment, or is not little endian.
|
||||||
|
x0 += sigma0
|
||||||
|
x1 += sigma1
|
||||||
|
x2 += sigma2
|
||||||
|
x3 += sigma3
|
||||||
|
x4 += x[4]
|
||||||
|
x5 += x[5]
|
||||||
|
x6 += x[6]
|
||||||
|
x7 += x[7]
|
||||||
|
x8 += x[8]
|
||||||
|
x9 += x[9]
|
||||||
|
x10 += x[10]
|
||||||
|
x11 += x[11]
|
||||||
|
x12 += x[12]
|
||||||
|
x13 += x[13]
|
||||||
|
x14 += x[14]
|
||||||
|
x15 += x[15]
|
||||||
|
if in != nil {
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], binary.LittleEndian.Uint32(in[0:4])^x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], binary.LittleEndian.Uint32(in[4:8])^x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], binary.LittleEndian.Uint32(in[8:12])^x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], binary.LittleEndian.Uint32(in[12:16])^x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], binary.LittleEndian.Uint32(in[16:20])^x4)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], binary.LittleEndian.Uint32(in[20:24])^x5)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], binary.LittleEndian.Uint32(in[24:28])^x6)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], binary.LittleEndian.Uint32(in[28:32])^x7)
|
||||||
|
binary.LittleEndian.PutUint32(out[32:36], binary.LittleEndian.Uint32(in[32:36])^x8)
|
||||||
|
binary.LittleEndian.PutUint32(out[36:40], binary.LittleEndian.Uint32(in[36:40])^x9)
|
||||||
|
binary.LittleEndian.PutUint32(out[40:44], binary.LittleEndian.Uint32(in[40:44])^x10)
|
||||||
|
binary.LittleEndian.PutUint32(out[44:48], binary.LittleEndian.Uint32(in[44:48])^x11)
|
||||||
|
binary.LittleEndian.PutUint32(out[48:52], binary.LittleEndian.Uint32(in[48:52])^x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[52:56], binary.LittleEndian.Uint32(in[52:56])^x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[56:60], binary.LittleEndian.Uint32(in[56:60])^x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[60:64], binary.LittleEndian.Uint32(in[60:64])^x15)
|
||||||
|
in = in[BlockSize:]
|
||||||
|
} else {
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], x4)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], x5)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], x6)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], x7)
|
||||||
|
binary.LittleEndian.PutUint32(out[32:36], x8)
|
||||||
|
binary.LittleEndian.PutUint32(out[36:40], x9)
|
||||||
|
binary.LittleEndian.PutUint32(out[40:44], x10)
|
||||||
|
binary.LittleEndian.PutUint32(out[44:48], x11)
|
||||||
|
binary.LittleEndian.PutUint32(out[48:52], x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[52:56], x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[56:60], x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[60:64], x15)
|
||||||
|
}
|
||||||
|
out = out[BlockSize:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stoping at 2^70 bytes per nonce is the user's responsibility.
|
||||||
|
ctr := uint64(x[13])<<32 | uint64(x[12])
|
||||||
|
ctr++
|
||||||
|
x[12] = uint32(ctr)
|
||||||
|
x[13] = uint32(ctr >> 32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hChaChaRef(x *[stateSize]uint32, out *[32]byte) {
|
||||||
|
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
|
||||||
|
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11]
|
||||||
|
|
||||||
|
for i := chachaRounds; i > 0; i -= 2 {
|
||||||
|
// quarterround(x, 0, 4, 8, 12)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = (x12 << 16) | (x12 >> 16)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = (x4 << 12) | (x4 >> 20)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = (x12 << 8) | (x12 >> 24)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = (x4 << 7) | (x4 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 5, 9, 13)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = (x13 << 16) | (x13 >> 16)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = (x5 << 12) | (x5 >> 20)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = (x13 << 8) | (x13 >> 24)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = (x5 << 7) | (x5 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 6, 10, 14)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = (x14 << 16) | (x14 >> 16)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = (x6 << 12) | (x6 >> 20)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = (x14 << 8) | (x14 >> 24)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = (x6 << 7) | (x6 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 7, 11, 15)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = (x15 << 16) | (x15 >> 16)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = (x7 << 12) | (x7 >> 20)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = (x15 << 8) | (x15 >> 24)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = (x7 << 7) | (x7 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 0, 5, 10, 15)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = (x15 << 16) | (x15 >> 16)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = (x5 << 12) | (x5 >> 20)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = (x15 << 8) | (x15 >> 24)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = (x5 << 7) | (x5 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 6, 11, 12)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = (x12 << 16) | (x12 >> 16)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = (x6 << 12) | (x6 >> 20)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = (x12 << 8) | (x12 >> 24)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = (x6 << 7) | (x6 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 7, 8, 13)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = (x13 << 16) | (x13 >> 16)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = (x7 << 12) | (x7 >> 20)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = (x13 << 8) | (x13 >> 24)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = (x7 << 7) | (x7 >> 25)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 4, 9, 14)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = (x14 << 16) | (x14 >> 16)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = (x4 << 12) | (x4 >> 20)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = (x14 << 8) | (x14 >> 24)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = (x4 << 7) | (x4 >> 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HChaCha returns x0...x3 | x12...x15, which corresponds to the
|
||||||
|
// indexes of the ChaCha constant and the indexes of the IV.
|
||||||
|
if useUnsafe {
|
||||||
|
outArr := (*[16]uint32)(unsafe.Pointer(&out[0]))
|
||||||
|
outArr[0] = x0
|
||||||
|
outArr[1] = x1
|
||||||
|
outArr[2] = x2
|
||||||
|
outArr[3] = x3
|
||||||
|
outArr[4] = x12
|
||||||
|
outArr[5] = x13
|
||||||
|
outArr[6] = x14
|
||||||
|
outArr[7] = x15
|
||||||
|
} else {
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], x15)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
395
vendor/github.com/Yawning/chacha20/chacha20_ref_go19.go
generated
vendored
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
// chacha20_ref.go - Reference ChaCha20.
|
||||||
|
//
|
||||||
|
// To the extent possible under law, Yawning Angel has waived all copyright
|
||||||
|
// and related or neighboring rights to chacha20, using the Creative
|
||||||
|
// Commons "CC0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
"math/bits"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func blocksRef(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
|
||||||
|
if isIetf {
|
||||||
|
var totalBlocks uint64
|
||||||
|
totalBlocks = uint64(x[12]) + uint64(nrBlocks)
|
||||||
|
if totalBlocks > math.MaxUint32 {
|
||||||
|
panic("chacha20: Exceeded keystream per nonce limit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This routine ignores x[0]...x[4] in favor the const values since it's
|
||||||
|
// ever so slightly faster.
|
||||||
|
|
||||||
|
for n := 0; n < nrBlocks; n++ {
|
||||||
|
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
|
||||||
|
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15]
|
||||||
|
|
||||||
|
for i := chachaRounds; i > 0; i -= 2 {
|
||||||
|
// quarterround(x, 0, 4, 8, 12)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = bits.RotateLeft32(x12, 16)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = bits.RotateLeft32(x4, 12)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = bits.RotateLeft32(x12, 8)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = bits.RotateLeft32(x4, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 5, 9, 13)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = bits.RotateLeft32(x13, 16)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = bits.RotateLeft32(x5, 12)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = bits.RotateLeft32(x13, 8)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = bits.RotateLeft32(x5, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 6, 10, 14)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = bits.RotateLeft32(x14, 16)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = bits.RotateLeft32(x6, 12)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = bits.RotateLeft32(x14, 8)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = bits.RotateLeft32(x6, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 7, 11, 15)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = bits.RotateLeft32(x15, 16)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = bits.RotateLeft32(x7, 12)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = bits.RotateLeft32(x15, 8)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = bits.RotateLeft32(x7, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 0, 5, 10, 15)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = bits.RotateLeft32(x15, 16)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = bits.RotateLeft32(x5, 12)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = bits.RotateLeft32(x15, 8)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = bits.RotateLeft32(x5, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 6, 11, 12)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = bits.RotateLeft32(x12, 16)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = bits.RotateLeft32(x6, 12)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = bits.RotateLeft32(x12, 8)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = bits.RotateLeft32(x6, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 7, 8, 13)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = bits.RotateLeft32(x13, 16)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = bits.RotateLeft32(x7, 12)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = bits.RotateLeft32(x13, 8)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = bits.RotateLeft32(x7, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 4, 9, 14)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = bits.RotateLeft32(x14, 16)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = bits.RotateLeft32(x4, 12)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = bits.RotateLeft32(x14, 8)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = bits.RotateLeft32(x4, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On amd64 at least, this is a rather big boost.
|
||||||
|
if useUnsafe {
|
||||||
|
if in != nil {
|
||||||
|
inArr := (*[16]uint32)(unsafe.Pointer(&in[n*BlockSize]))
|
||||||
|
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
|
||||||
|
outArr[0] = inArr[0] ^ (x0 + sigma0)
|
||||||
|
outArr[1] = inArr[1] ^ (x1 + sigma1)
|
||||||
|
outArr[2] = inArr[2] ^ (x2 + sigma2)
|
||||||
|
outArr[3] = inArr[3] ^ (x3 + sigma3)
|
||||||
|
outArr[4] = inArr[4] ^ (x4 + x[4])
|
||||||
|
outArr[5] = inArr[5] ^ (x5 + x[5])
|
||||||
|
outArr[6] = inArr[6] ^ (x6 + x[6])
|
||||||
|
outArr[7] = inArr[7] ^ (x7 + x[7])
|
||||||
|
outArr[8] = inArr[8] ^ (x8 + x[8])
|
||||||
|
outArr[9] = inArr[9] ^ (x9 + x[9])
|
||||||
|
outArr[10] = inArr[10] ^ (x10 + x[10])
|
||||||
|
outArr[11] = inArr[11] ^ (x11 + x[11])
|
||||||
|
outArr[12] = inArr[12] ^ (x12 + x[12])
|
||||||
|
outArr[13] = inArr[13] ^ (x13 + x[13])
|
||||||
|
outArr[14] = inArr[14] ^ (x14 + x[14])
|
||||||
|
outArr[15] = inArr[15] ^ (x15 + x[15])
|
||||||
|
} else {
|
||||||
|
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
|
||||||
|
outArr[0] = x0 + sigma0
|
||||||
|
outArr[1] = x1 + sigma1
|
||||||
|
outArr[2] = x2 + sigma2
|
||||||
|
outArr[3] = x3 + sigma3
|
||||||
|
outArr[4] = x4 + x[4]
|
||||||
|
outArr[5] = x5 + x[5]
|
||||||
|
outArr[6] = x6 + x[6]
|
||||||
|
outArr[7] = x7 + x[7]
|
||||||
|
outArr[8] = x8 + x[8]
|
||||||
|
outArr[9] = x9 + x[9]
|
||||||
|
outArr[10] = x10 + x[10]
|
||||||
|
outArr[11] = x11 + x[11]
|
||||||
|
outArr[12] = x12 + x[12]
|
||||||
|
outArr[13] = x13 + x[13]
|
||||||
|
outArr[14] = x14 + x[14]
|
||||||
|
outArr[15] = x15 + x[15]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Slow path, either the architecture cares about alignment, or is not little endian.
|
||||||
|
x0 += sigma0
|
||||||
|
x1 += sigma1
|
||||||
|
x2 += sigma2
|
||||||
|
x3 += sigma3
|
||||||
|
x4 += x[4]
|
||||||
|
x5 += x[5]
|
||||||
|
x6 += x[6]
|
||||||
|
x7 += x[7]
|
||||||
|
x8 += x[8]
|
||||||
|
x9 += x[9]
|
||||||
|
x10 += x[10]
|
||||||
|
x11 += x[11]
|
||||||
|
x12 += x[12]
|
||||||
|
x13 += x[13]
|
||||||
|
x14 += x[14]
|
||||||
|
x15 += x[15]
|
||||||
|
if in != nil {
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], binary.LittleEndian.Uint32(in[0:4])^x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], binary.LittleEndian.Uint32(in[4:8])^x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], binary.LittleEndian.Uint32(in[8:12])^x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], binary.LittleEndian.Uint32(in[12:16])^x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], binary.LittleEndian.Uint32(in[16:20])^x4)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], binary.LittleEndian.Uint32(in[20:24])^x5)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], binary.LittleEndian.Uint32(in[24:28])^x6)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], binary.LittleEndian.Uint32(in[28:32])^x7)
|
||||||
|
binary.LittleEndian.PutUint32(out[32:36], binary.LittleEndian.Uint32(in[32:36])^x8)
|
||||||
|
binary.LittleEndian.PutUint32(out[36:40], binary.LittleEndian.Uint32(in[36:40])^x9)
|
||||||
|
binary.LittleEndian.PutUint32(out[40:44], binary.LittleEndian.Uint32(in[40:44])^x10)
|
||||||
|
binary.LittleEndian.PutUint32(out[44:48], binary.LittleEndian.Uint32(in[44:48])^x11)
|
||||||
|
binary.LittleEndian.PutUint32(out[48:52], binary.LittleEndian.Uint32(in[48:52])^x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[52:56], binary.LittleEndian.Uint32(in[52:56])^x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[56:60], binary.LittleEndian.Uint32(in[56:60])^x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[60:64], binary.LittleEndian.Uint32(in[60:64])^x15)
|
||||||
|
in = in[BlockSize:]
|
||||||
|
} else {
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], x4)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], x5)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], x6)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], x7)
|
||||||
|
binary.LittleEndian.PutUint32(out[32:36], x8)
|
||||||
|
binary.LittleEndian.PutUint32(out[36:40], x9)
|
||||||
|
binary.LittleEndian.PutUint32(out[40:44], x10)
|
||||||
|
binary.LittleEndian.PutUint32(out[44:48], x11)
|
||||||
|
binary.LittleEndian.PutUint32(out[48:52], x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[52:56], x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[56:60], x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[60:64], x15)
|
||||||
|
}
|
||||||
|
out = out[BlockSize:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stoping at 2^70 bytes per nonce is the user's responsibility.
|
||||||
|
ctr := uint64(x[13])<<32 | uint64(x[12])
|
||||||
|
ctr++
|
||||||
|
x[12] = uint32(ctr)
|
||||||
|
x[13] = uint32(ctr >> 32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hChaChaRef(x *[stateSize]uint32, out *[32]byte) {
|
||||||
|
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
|
||||||
|
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11]
|
||||||
|
|
||||||
|
for i := chachaRounds; i > 0; i -= 2 {
|
||||||
|
// quarterround(x, 0, 4, 8, 12)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = bits.RotateLeft32(x12, 16)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = bits.RotateLeft32(x4, 12)
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = bits.RotateLeft32(x12, 8)
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = bits.RotateLeft32(x4, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 5, 9, 13)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = bits.RotateLeft32(x13, 16)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = bits.RotateLeft32(x5, 12)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = bits.RotateLeft32(x13, 8)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = bits.RotateLeft32(x5, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 6, 10, 14)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = bits.RotateLeft32(x14, 16)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = bits.RotateLeft32(x6, 12)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = bits.RotateLeft32(x14, 8)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = bits.RotateLeft32(x6, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 7, 11, 15)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = bits.RotateLeft32(x15, 16)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = bits.RotateLeft32(x7, 12)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = bits.RotateLeft32(x15, 8)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = bits.RotateLeft32(x7, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 0, 5, 10, 15)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = bits.RotateLeft32(x15, 16)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = bits.RotateLeft32(x5, 12)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = bits.RotateLeft32(x15, 8)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = bits.RotateLeft32(x5, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 1, 6, 11, 12)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = bits.RotateLeft32(x12, 16)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = bits.RotateLeft32(x6, 12)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = bits.RotateLeft32(x12, 8)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = bits.RotateLeft32(x6, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 2, 7, 8, 13)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = bits.RotateLeft32(x13, 16)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = bits.RotateLeft32(x7, 12)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = bits.RotateLeft32(x13, 8)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = bits.RotateLeft32(x7, 7)
|
||||||
|
|
||||||
|
// quarterround(x, 3, 4, 9, 14)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = bits.RotateLeft32(x14, 16)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = bits.RotateLeft32(x4, 12)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = bits.RotateLeft32(x14, 8)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = bits.RotateLeft32(x4, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HChaCha returns x0...x3 | x12...x15, which corresponds to the
|
||||||
|
// indexes of the ChaCha constant and the indexes of the IV.
|
||||||
|
if useUnsafe {
|
||||||
|
outArr := (*[16]uint32)(unsafe.Pointer(&out[0]))
|
||||||
|
outArr[0] = x0
|
||||||
|
outArr[1] = x1
|
||||||
|
outArr[2] = x2
|
||||||
|
outArr[3] = x3
|
||||||
|
outArr[4] = x12
|
||||||
|
outArr[5] = x13
|
||||||
|
outArr[6] = x14
|
||||||
|
outArr[7] = x15
|
||||||
|
} else {
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], x15)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Go's `text/template` package with newline elision
|
||||||
|
|
||||||
|
This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline.
|
||||||
|
|
||||||
|
eg.
|
||||||
|
|
||||||
|
```
|
||||||
|
{{if true}}\
|
||||||
|
hello
|
||||||
|
{{end}}\
|
||||||
|
```
|
||||||
|
|
||||||
|
Will result in:
|
||||||
|
|
||||||
|
```
|
||||||
|
hello\n
|
||||||
|
```
|
||||||
|
|
||||||
|
Rather than:
|
||||||
|
|
||||||
|
```
|
||||||
|
\n
|
||||||
|
hello\n
|
||||||
|
\n
|
||||||
|
```
|
||||||
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package template implements data-driven templates for generating textual output.
|
||||||
|
|
||||||
|
To generate HTML output, see package html/template, which has the same interface
|
||||||
|
as this package but automatically secures HTML output against certain attacks.
|
||||||
|
|
||||||
|
Templates are executed by applying them to a data structure. Annotations in the
|
||||||
|
template refer to elements of the data structure (typically a field of a struct
|
||||||
|
or a key in a map) to control execution and derive values to be displayed.
|
||||||
|
Execution of the template walks the structure and sets the cursor, represented
|
||||||
|
by a period '.' and called "dot", to the value at the current location in the
|
||||||
|
structure as execution proceeds.
|
||||||
|
|
||||||
|
The input text for a template is UTF-8-encoded text in any format.
|
||||||
|
"Actions"--data evaluations or control structures--are delimited by
|
||||||
|
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||||
|
Actions may not span newlines, although comments can.
|
||||||
|
|
||||||
|
Once parsed, a template may be executed safely in parallel.
|
||||||
|
|
||||||
|
Here is a trivial example that prints "17 items are made of wool".
|
||||||
|
|
||||||
|
type Inventory struct {
|
||||||
|
Material string
|
||||||
|
Count uint
|
||||||
|
}
|
||||||
|
sweaters := Inventory{"wool", 17}
|
||||||
|
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
err = tmpl.Execute(os.Stdout, sweaters)
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
|
||||||
|
More intricate examples appear below.
|
||||||
|
|
||||||
|
Actions
|
||||||
|
|
||||||
|
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||||
|
data, defined in detail below.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// {{/* a comment */}}
|
||||||
|
// A comment; discarded. May contain newlines.
|
||||||
|
// Comments do not nest and must start and end at the
|
||||||
|
// delimiters, as shown here.
|
||||||
|
/*
|
||||||
|
|
||||||
|
{{pipeline}}
|
||||||
|
The default textual representation of the value of the pipeline
|
||||||
|
is copied to the output.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{end}}
|
||||||
|
If the value of the pipeline is empty, no output is generated;
|
||||||
|
otherwise, T1 is executed. The empty values are false, 0, any
|
||||||
|
nil pointer or interface value, and any array, slice, map, or
|
||||||
|
string of length zero.
|
||||||
|
Dot is unaffected.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
If the value of the pipeline is empty, T0 is executed;
|
||||||
|
otherwise, T1 is executed. Dot is unaffected.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||||
|
To simplify the appearance of if-else chains, the else action
|
||||||
|
of an if may include another if directly; the effect is exactly
|
||||||
|
the same as writing
|
||||||
|
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||||
|
|
||||||
|
{{range pipeline}} T1 {{end}}
|
||||||
|
The value of the pipeline must be an array, slice, map, or channel.
|
||||||
|
If the value of the pipeline has length zero, nothing is output;
|
||||||
|
otherwise, dot is set to the successive elements of the array,
|
||||||
|
slice, or map and T1 is executed. If the value is a map and the
|
||||||
|
keys are of basic type with a defined order ("comparable"), the
|
||||||
|
elements will be visited in sorted key order.
|
||||||
|
|
||||||
|
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
The value of the pipeline must be an array, slice, map, or channel.
|
||||||
|
If the value of the pipeline has length zero, dot is unaffected and
|
||||||
|
T0 is executed; otherwise, dot is set to the successive elements
|
||||||
|
of the array, slice, or map and T1 is executed.
|
||||||
|
|
||||||
|
{{template "name"}}
|
||||||
|
The template with the specified name is executed with nil data.
|
||||||
|
|
||||||
|
{{template "name" pipeline}}
|
||||||
|
The template with the specified name is executed with dot set
|
||||||
|
to the value of the pipeline.
|
||||||
|
|
||||||
|
{{with pipeline}} T1 {{end}}
|
||||||
|
If the value of the pipeline is empty, no output is generated;
|
||||||
|
otherwise, dot is set to the value of the pipeline and T1 is
|
||||||
|
executed.
|
||||||
|
|
||||||
|
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
If the value of the pipeline is empty, dot is unaffected and T0
|
||||||
|
is executed; otherwise, dot is set to the value of the pipeline
|
||||||
|
and T1 is executed.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
|
||||||
|
An argument is a simple value, denoted by one of the following.
|
||||||
|
|
||||||
|
- A boolean, string, character, integer, floating-point, imaginary
|
||||||
|
or complex constant in Go syntax. These behave like Go's untyped
|
||||||
|
constants, although raw strings may not span newlines.
|
||||||
|
- The keyword nil, representing an untyped Go nil.
|
||||||
|
- The character '.' (period):
|
||||||
|
.
|
||||||
|
The result is the value of dot.
|
||||||
|
- A variable name, which is a (possibly empty) alphanumeric string
|
||||||
|
preceded by a dollar sign, such as
|
||||||
|
$piOver2
|
||||||
|
or
|
||||||
|
$
|
||||||
|
The result is the value of the variable.
|
||||||
|
Variables are described below.
|
||||||
|
- The name of a field of the data, which must be a struct, preceded
|
||||||
|
by a period, such as
|
||||||
|
.Field
|
||||||
|
The result is the value of the field. Field invocations may be
|
||||||
|
chained:
|
||||||
|
.Field1.Field2
|
||||||
|
Fields can also be evaluated on variables, including chaining:
|
||||||
|
$x.Field1.Field2
|
||||||
|
- The name of a key of the data, which must be a map, preceded
|
||||||
|
by a period, such as
|
||||||
|
.Key
|
||||||
|
The result is the map element value indexed by the key.
|
||||||
|
Key invocations may be chained and combined with fields to any
|
||||||
|
depth:
|
||||||
|
.Field1.Key1.Field2.Key2
|
||||||
|
Although the key must be an alphanumeric identifier, unlike with
|
||||||
|
field names they do not need to start with an upper case letter.
|
||||||
|
Keys can also be evaluated on variables, including chaining:
|
||||||
|
$x.key1.key2
|
||||||
|
- The name of a niladic method of the data, preceded by a period,
|
||||||
|
such as
|
||||||
|
.Method
|
||||||
|
The result is the value of invoking the method with dot as the
|
||||||
|
receiver, dot.Method(). Such a method must have one return value (of
|
||||||
|
any type) or two return values, the second of which is an error.
|
||||||
|
If it has two and the returned error is non-nil, execution terminates
|
||||||
|
and an error is returned to the caller as the value of Execute.
|
||||||
|
Method invocations may be chained and combined with fields and keys
|
||||||
|
to any depth:
|
||||||
|
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||||
|
Methods can also be evaluated on variables, including chaining:
|
||||||
|
$x.Method1.Field
|
||||||
|
- The name of a niladic function, such as
|
||||||
|
fun
|
||||||
|
The result is the value of invoking the function, fun(). The return
|
||||||
|
types and values behave as in methods. Functions and function
|
||||||
|
names are described below.
|
||||||
|
- A parenthesized instance of one the above, for grouping. The result
|
||||||
|
may be accessed by a field or map key invocation.
|
||||||
|
print (.F1 arg1) (.F2 arg2)
|
||||||
|
(.StructValuedMethod "arg").Field
|
||||||
|
|
||||||
|
Arguments may evaluate to any type; if they are pointers the implementation
|
||||||
|
automatically indirects to the base type when required.
|
||||||
|
If an evaluation yields a function value, such as a function-valued
|
||||||
|
field of a struct, the function is not invoked automatically, but it
|
||||||
|
can be used as a truth value for an if action and the like. To invoke
|
||||||
|
it, use the call function, defined below.
|
||||||
|
|
||||||
|
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||||
|
value (argument) or a function or method call, possibly with multiple arguments:
|
||||||
|
|
||||||
|
Argument
|
||||||
|
The result is the value of evaluating the argument.
|
||||||
|
.Method [Argument...]
|
||||||
|
The method can be alone or the last element of a chain but,
|
||||||
|
unlike methods in the middle of a chain, it can take arguments.
|
||||||
|
The result is the value of calling the method with the
|
||||||
|
arguments:
|
||||||
|
dot.Method(Argument1, etc.)
|
||||||
|
functionName [Argument...]
|
||||||
|
The result is the value of calling the function associated
|
||||||
|
with the name:
|
||||||
|
function(Argument1, etc.)
|
||||||
|
Functions and function names are described below.
|
||||||
|
|
||||||
|
Pipelines
|
||||||
|
|
||||||
|
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||||
|
characters '|'. In a chained pipeline, the result of the each command is
|
||||||
|
passed as the last argument of the following command. The output of the final
|
||||||
|
command in the pipeline is the value of the pipeline.
|
||||||
|
|
||||||
|
The output of a command will be either one value or two values, the second of
|
||||||
|
which has type error. If that second value is present and evaluates to
|
||||||
|
non-nil, execution terminates and the error is returned to the caller of
|
||||||
|
Execute.
|
||||||
|
|
||||||
|
Variables
|
||||||
|
|
||||||
|
A pipeline inside an action may initialize a variable to capture the result.
|
||||||
|
The initialization has syntax
|
||||||
|
|
||||||
|
$variable := pipeline
|
||||||
|
|
||||||
|
where $variable is the name of the variable. An action that declares a
|
||||||
|
variable produces no output.
|
||||||
|
|
||||||
|
If a "range" action initializes a variable, the variable is set to the
|
||||||
|
successive elements of the iteration. Also, a "range" may declare two
|
||||||
|
variables, separated by a comma:
|
||||||
|
|
||||||
|
range $index, $element := pipeline
|
||||||
|
|
||||||
|
in which case $index and $element are set to the successive values of the
|
||||||
|
array/slice index or map key and element, respectively. Note that if there is
|
||||||
|
only one variable, it is assigned the element; this is opposite to the
|
||||||
|
convention in Go range clauses.
|
||||||
|
|
||||||
|
A variable's scope extends to the "end" action of the control structure ("if",
|
||||||
|
"with", or "range") in which it is declared, or to the end of the template if
|
||||||
|
there is no such control structure. A template invocation does not inherit
|
||||||
|
variables from the point of its invocation.
|
||||||
|
|
||||||
|
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||||
|
to the starting value of dot.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
Here are some example one-line templates demonstrating pipelines and variables.
|
||||||
|
All produce the quoted word "output":
|
||||||
|
|
||||||
|
{{"\"output\""}}
|
||||||
|
A string constant.
|
||||||
|
{{`"output"`}}
|
||||||
|
A raw string constant.
|
||||||
|
{{printf "%q" "output"}}
|
||||||
|
A function call.
|
||||||
|
{{"output" | printf "%q"}}
|
||||||
|
A function call whose final argument comes from the previous
|
||||||
|
command.
|
||||||
|
{{printf "%q" (print "out" "put")}}
|
||||||
|
A parenthesized argument.
|
||||||
|
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||||
|
A more elaborate call.
|
||||||
|
{{"output" | printf "%s" | printf "%q"}}
|
||||||
|
A longer chain.
|
||||||
|
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||||
|
A with action using dot.
|
||||||
|
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||||
|
A with action that creates and uses a variable.
|
||||||
|
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||||
|
A with action that uses the variable in another action.
|
||||||
|
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||||
|
The same, but pipelined.
|
||||||
|
|
||||||
|
Functions
|
||||||
|
|
||||||
|
During execution functions are found in two function maps: first in the
|
||||||
|
template, then in the global function map. By default, no functions are defined
|
||||||
|
in the template but the Funcs method can be used to add them.
|
||||||
|
|
||||||
|
Predefined global functions are named as follows.
|
||||||
|
|
||||||
|
and
|
||||||
|
Returns the boolean AND of its arguments by returning the
|
||||||
|
first empty argument or the last argument, that is,
|
||||||
|
"and x y" behaves as "if x then y else x". All the
|
||||||
|
arguments are evaluated.
|
||||||
|
call
|
||||||
|
Returns the result of calling the first argument, which
|
||||||
|
must be a function, with the remaining arguments as parameters.
|
||||||
|
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||||
|
Y is a func-valued field, map entry, or the like.
|
||||||
|
The first argument must be the result of an evaluation
|
||||||
|
that yields a value of function type (as distinct from
|
||||||
|
a predefined function such as print). The function must
|
||||||
|
return either one or two result values, the second of which
|
||||||
|
is of type error. If the arguments don't match the function
|
||||||
|
or the returned error value is non-nil, execution stops.
|
||||||
|
html
|
||||||
|
Returns the escaped HTML equivalent of the textual
|
||||||
|
representation of its arguments.
|
||||||
|
index
|
||||||
|
Returns the result of indexing its first argument by the
|
||||||
|
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||||
|
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||||
|
js
|
||||||
|
Returns the escaped JavaScript equivalent of the textual
|
||||||
|
representation of its arguments.
|
||||||
|
len
|
||||||
|
Returns the integer length of its argument.
|
||||||
|
not
|
||||||
|
Returns the boolean negation of its single argument.
|
||||||
|
or
|
||||||
|
Returns the boolean OR of its arguments by returning the
|
||||||
|
first non-empty argument or the last argument, that is,
|
||||||
|
"or x y" behaves as "if x then x else y". All the
|
||||||
|
arguments are evaluated.
|
||||||
|
print
|
||||||
|
An alias for fmt.Sprint
|
||||||
|
printf
|
||||||
|
An alias for fmt.Sprintf
|
||||||
|
println
|
||||||
|
An alias for fmt.Sprintln
|
||||||
|
urlquery
|
||||||
|
Returns the escaped value of the textual representation of
|
||||||
|
its arguments in a form suitable for embedding in a URL query.
|
||||||
|
|
||||||
|
The boolean functions take any zero value to be false and a non-zero
|
||||||
|
value to be true.
|
||||||
|
|
||||||
|
There is also a set of binary comparison operators defined as
|
||||||
|
functions:
|
||||||
|
|
||||||
|
eq
|
||||||
|
Returns the boolean truth of arg1 == arg2
|
||||||
|
ne
|
||||||
|
Returns the boolean truth of arg1 != arg2
|
||||||
|
lt
|
||||||
|
Returns the boolean truth of arg1 < arg2
|
||||||
|
le
|
||||||
|
Returns the boolean truth of arg1 <= arg2
|
||||||
|
gt
|
||||||
|
Returns the boolean truth of arg1 > arg2
|
||||||
|
ge
|
||||||
|
Returns the boolean truth of arg1 >= arg2
|
||||||
|
|
||||||
|
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||||
|
arguments and compares the second and subsequent to the first,
|
||||||
|
returning in effect
|
||||||
|
|
||||||
|
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||||
|
|
||||||
|
(Unlike with || in Go, however, eq is a function call and all the
|
||||||
|
arguments will be evaluated.)
|
||||||
|
|
||||||
|
The comparison functions work on basic types only (or named basic
|
||||||
|
types, such as "type Celsius float32"). They implement the Go rules
|
||||||
|
for comparison of values, except that size and exact type are
|
||||||
|
ignored, so any integer value, signed or unsigned, may be compared
|
||||||
|
with any other integer value. (The arithmetic value is compared,
|
||||||
|
not the bit pattern, so all negative integers are less than all
|
||||||
|
unsigned integers.) However, as usual, one may not compare an int
|
||||||
|
with a float32 and so on.
|
||||||
|
|
||||||
|
Associated templates
|
||||||
|
|
||||||
|
Each template is named by a string specified when it is created. Also, each
|
||||||
|
template is associated with zero or more other templates that it may invoke by
|
||||||
|
name; such associations are transitive and form a name space of templates.
|
||||||
|
|
||||||
|
A template may use a template invocation to instantiate another associated
|
||||||
|
template; see the explanation of the "template" action above. The name must be
|
||||||
|
that of a template associated with the template that contains the invocation.
|
||||||
|
|
||||||
|
Nested template definitions
|
||||||
|
|
||||||
|
When parsing a template, another template may be defined and associated with the
|
||||||
|
template being parsed. Template definitions must appear at the top level of the
|
||||||
|
template, much like global variables in a Go program.
|
||||||
|
|
||||||
|
The syntax of such definitions is to surround each template declaration with a
|
||||||
|
"define" and "end" action.
|
||||||
|
|
||||||
|
The define action names the template being created by providing a string
|
||||||
|
constant. Here is a simple example:
|
||||||
|
|
||||||
|
`{{define "T1"}}ONE{{end}}
|
||||||
|
{{define "T2"}}TWO{{end}}
|
||||||
|
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||||
|
{{template "T3"}}`
|
||||||
|
|
||||||
|
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||||
|
when it is executed. Finally it invokes T3. If executed this template will
|
||||||
|
produce the text
|
||||||
|
|
||||||
|
ONE TWO
|
||||||
|
|
||||||
|
By construction, a template may reside in only one association. If it's
|
||||||
|
necessary to have a template addressable from multiple associations, the
|
||||||
|
template definition must be parsed multiple times to create distinct *Template
|
||||||
|
values, or must be copied with the Clone or AddParseTree method.
|
||||||
|
|
||||||
|
Parse may be called multiple times to assemble the various associated templates;
|
||||||
|
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||||
|
related templates stored in files.
|
||||||
|
|
||||||
|
A template may be executed directly or through ExecuteTemplate, which executes
|
||||||
|
an associated template identified by name. To invoke our example above, we
|
||||||
|
might write,
|
||||||
|
|
||||||
|
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execution failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
or to invoke a particular template explicitly by name,
|
||||||
|
|
||||||
|
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execution failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package template
|
||||||
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
Normal file
@ -0,0 +1,845 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// state represents the state of an execution. It's not part of the
|
||||||
|
// template so that multiple executions of the same template
|
||||||
|
// can execute in parallel.
|
||||||
|
type state struct {
|
||||||
|
tmpl *Template
|
||||||
|
wr io.Writer
|
||||||
|
node parse.Node // current node, for errors
|
||||||
|
vars []variable // push-down stack of variable values.
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||||
|
type variable struct {
|
||||||
|
name string
|
||||||
|
value reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// push pushes a new variable on the stack.
|
||||||
|
func (s *state) push(name string, value reflect.Value) {
|
||||||
|
s.vars = append(s.vars, variable{name, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark returns the length of the variable stack.
|
||||||
|
func (s *state) mark() int {
|
||||||
|
return len(s.vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop pops the variable stack up to the mark.
|
||||||
|
func (s *state) pop(mark int) {
|
||||||
|
s.vars = s.vars[0:mark]
|
||||||
|
}
|
||||||
|
|
||||||
|
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||||
|
func (s *state) setVar(n int, value reflect.Value) {
|
||||||
|
s.vars[len(s.vars)-n].value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// varValue returns the value of the named variable.
|
||||||
|
func (s *state) varValue(name string) reflect.Value {
|
||||||
|
for i := s.mark() - 1; i >= 0; i-- {
|
||||||
|
if s.vars[i].name == name {
|
||||||
|
return s.vars[i].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.errorf("undefined variable: %s", name)
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
var zero reflect.Value
|
||||||
|
|
||||||
|
// at marks the state to be on node n, for error reporting.
|
||||||
|
func (s *state) at(node parse.Node) {
|
||||||
|
s.node = node
|
||||||
|
}
|
||||||
|
|
||||||
|
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||||
|
// so it can be used safely inside a Printf format string.
|
||||||
|
func doublePercent(str string) string {
|
||||||
|
if strings.Contains(str, "%") {
|
||||||
|
str = strings.Replace(str, "%", "%%", -1)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf formats the error and terminates processing.
|
||||||
|
func (s *state) errorf(format string, args ...interface{}) {
|
||||||
|
name := doublePercent(s.tmpl.Name())
|
||||||
|
if s.node == nil {
|
||||||
|
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||||
|
} else {
|
||||||
|
location, context := s.tmpl.ErrorContext(s.node)
|
||||||
|
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// errRecover is the handler that turns panics into returns from the top
|
||||||
|
// level of Parse.
|
||||||
|
func errRecover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
switch err := e.(type) {
|
||||||
|
case runtime.Error:
|
||||||
|
panic(e)
|
||||||
|
case error:
|
||||||
|
*errp = err
|
||||||
|
default:
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteTemplate applies the template associated with t that has the given name
|
||||||
|
// to the specified data object and writes the output to wr.
|
||||||
|
// If an error occurs executing the template or writing its output,
|
||||||
|
// execution stops, but partial results may already have been written to
|
||||||
|
// the output writer.
|
||||||
|
// A template may be executed safely in parallel.
|
||||||
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||||
|
tmpl := t.tmpl[name]
|
||||||
|
if tmpl == nil {
|
||||||
|
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||||
|
}
|
||||||
|
return tmpl.Execute(wr, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute applies a parsed template to the specified data object,
|
||||||
|
// and writes the output to wr.
|
||||||
|
// If an error occurs executing the template or writing its output,
|
||||||
|
// execution stops, but partial results may already have been written to
|
||||||
|
// the output writer.
|
||||||
|
// A template may be executed safely in parallel.
|
||||||
|
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||||
|
defer errRecover(&err)
|
||||||
|
value := reflect.ValueOf(data)
|
||||||
|
state := &state{
|
||||||
|
tmpl: t,
|
||||||
|
wr: wr,
|
||||||
|
vars: []variable{{"$", value}},
|
||||||
|
}
|
||||||
|
t.init()
|
||||||
|
if t.Tree == nil || t.Root == nil {
|
||||||
|
var b bytes.Buffer
|
||||||
|
for name, tmpl := range t.tmpl {
|
||||||
|
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "%q", name)
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
if b.Len() > 0 {
|
||||||
|
s = "; defined templates are: " + b.String()
|
||||||
|
}
|
||||||
|
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
||||||
|
}
|
||||||
|
state.walk(value, t.Root)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk functions step through the major pieces of the template structure,
|
||||||
|
// generating output as they go.
|
||||||
|
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||||
|
s.at(node)
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *parse.ActionNode:
|
||||||
|
// Do not pop variables so they persist until next end.
|
||||||
|
// Also, if the action declares variables, don't print the result.
|
||||||
|
val := s.evalPipeline(dot, node.Pipe)
|
||||||
|
if len(node.Pipe.Decl) == 0 {
|
||||||
|
s.printValue(node, val)
|
||||||
|
}
|
||||||
|
case *parse.IfNode:
|
||||||
|
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||||
|
case *parse.ListNode:
|
||||||
|
for _, node := range node.Nodes {
|
||||||
|
s.walk(dot, node)
|
||||||
|
}
|
||||||
|
case *parse.RangeNode:
|
||||||
|
s.walkRange(dot, node)
|
||||||
|
case *parse.TemplateNode:
|
||||||
|
s.walkTemplate(dot, node)
|
||||||
|
case *parse.TextNode:
|
||||||
|
if _, err := s.wr.Write(node.Text); err != nil {
|
||||||
|
s.errorf("%s", err)
|
||||||
|
}
|
||||||
|
case *parse.WithNode:
|
||||||
|
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||||
|
default:
|
||||||
|
s.errorf("unknown node: %s", node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||||
|
// are identical in behavior except that 'with' sets dot.
|
||||||
|
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||||
|
defer s.pop(s.mark())
|
||||||
|
val := s.evalPipeline(dot, pipe)
|
||||||
|
truth, ok := isTrue(val)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("if/with can't use %v", val)
|
||||||
|
}
|
||||||
|
if truth {
|
||||||
|
if typ == parse.NodeWith {
|
||||||
|
s.walk(val, list)
|
||||||
|
} else {
|
||||||
|
s.walk(dot, list)
|
||||||
|
}
|
||||||
|
} else if elseList != nil {
|
||||||
|
s.walk(dot, elseList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||||
|
// and whether the value has a meaningful truth value.
|
||||||
|
func isTrue(val reflect.Value) (truth, ok bool) {
|
||||||
|
if !val.IsValid() {
|
||||||
|
// Something like var x interface{}, never set. It's a form of nil.
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
truth = val.Len() > 0
|
||||||
|
case reflect.Bool:
|
||||||
|
truth = val.Bool()
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
truth = val.Complex() != 0
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||||
|
truth = !val.IsNil()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
truth = val.Int() != 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
truth = val.Float() != 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
truth = val.Uint() != 0
|
||||||
|
case reflect.Struct:
|
||||||
|
truth = true // Struct values are always true.
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return truth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
|
s.at(r)
|
||||||
|
defer s.pop(s.mark())
|
||||||
|
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||||
|
// mark top of stack before any variables in the body are pushed.
|
||||||
|
mark := s.mark()
|
||||||
|
oneIteration := func(index, elem reflect.Value) {
|
||||||
|
// Set top var (lexically the second if there are two) to the element.
|
||||||
|
if len(r.Pipe.Decl) > 0 {
|
||||||
|
s.setVar(1, elem)
|
||||||
|
}
|
||||||
|
// Set next var (lexically the first if there are two) to the index.
|
||||||
|
if len(r.Pipe.Decl) > 1 {
|
||||||
|
s.setVar(2, index)
|
||||||
|
}
|
||||||
|
s.walk(elem, r.List)
|
||||||
|
s.pop(mark)
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Map:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, key := range sortKeys(val.MapKeys()) {
|
||||||
|
oneIteration(key, val.MapIndex(key))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Chan:
|
||||||
|
if val.IsNil() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for ; ; i++ {
|
||||||
|
elem, ok := val.Recv()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
oneIteration(reflect.ValueOf(i), elem)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Invalid:
|
||||||
|
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||||
|
default:
|
||||||
|
s.errorf("range can't iterate over %v", val)
|
||||||
|
}
|
||||||
|
if r.ElseList != nil {
|
||||||
|
s.walk(dot, r.ElseList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||||
|
s.at(t)
|
||||||
|
tmpl := s.tmpl.tmpl[t.Name]
|
||||||
|
if tmpl == nil {
|
||||||
|
s.errorf("template %q not defined", t.Name)
|
||||||
|
}
|
||||||
|
// Variables declared by the pipeline persist.
|
||||||
|
dot = s.evalPipeline(dot, t.Pipe)
|
||||||
|
newState := *s
|
||||||
|
newState.tmpl = tmpl
|
||||||
|
// No dynamic scoping: template invocations inherit no variables.
|
||||||
|
newState.vars = []variable{{"$", dot}}
|
||||||
|
newState.walk(dot, tmpl.Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||||
|
// values from the data structure by examining fields, calling methods, and so on.
|
||||||
|
// The printing of those values happens only through walk functions.
|
||||||
|
|
||||||
|
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||||
|
// pipeline has a variable declaration, the variable will be pushed on the
|
||||||
|
// stack. Callers should therefore pop the stack after they are finished
|
||||||
|
// executing commands depending on the pipeline value.
|
||||||
|
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||||
|
if pipe == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.at(pipe)
|
||||||
|
for _, cmd := range pipe.Cmds {
|
||||||
|
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||||
|
// If the object has type interface{}, dig down one level to the thing inside.
|
||||||
|
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||||
|
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, variable := range pipe.Decl {
|
||||||
|
s.push(variable.Ident[0], value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||||
|
if len(args) > 1 || final.IsValid() {
|
||||||
|
s.errorf("can't give argument to non-function %s", args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||||
|
firstWord := cmd.Args[0]
|
||||||
|
switch n := firstWord.(type) {
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||||
|
case *parse.ChainNode:
|
||||||
|
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
// Must be a function.
|
||||||
|
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||||
|
return s.evalPipeline(dot, n)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||||
|
}
|
||||||
|
s.at(firstWord)
|
||||||
|
s.notAFunction(cmd.Args, final)
|
||||||
|
switch word := firstWord.(type) {
|
||||||
|
case *parse.BoolNode:
|
||||||
|
return reflect.ValueOf(word.True)
|
||||||
|
case *parse.DotNode:
|
||||||
|
return dot
|
||||||
|
case *parse.NilNode:
|
||||||
|
s.errorf("nil is not a command")
|
||||||
|
case *parse.NumberNode:
|
||||||
|
return s.idealConstant(word)
|
||||||
|
case *parse.StringNode:
|
||||||
|
return reflect.ValueOf(word.Text)
|
||||||
|
}
|
||||||
|
s.errorf("can't evaluate command %q", firstWord)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// idealConstant is called to return the value of a number in a context where
|
||||||
|
// we don't know the type. In that case, the syntax of the number tells us
|
||||||
|
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||||
|
// a uint ideal constant in this situation - the value must be of int type.
|
||||||
|
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||||
|
// These are ideal constants but we don't know the type
|
||||||
|
// and we have no context. (If it was a method argument,
|
||||||
|
// we'd know what we need.) The syntax guides us to some extent.
|
||||||
|
s.at(constant)
|
||||||
|
switch {
|
||||||
|
case constant.IsComplex:
|
||||||
|
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||||
|
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||||
|
return reflect.ValueOf(constant.Float64)
|
||||||
|
case constant.IsInt:
|
||||||
|
n := int(constant.Int64)
|
||||||
|
if int64(n) != constant.Int64 {
|
||||||
|
s.errorf("%s overflows int", constant.Text)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(n)
|
||||||
|
case constant.IsUint:
|
||||||
|
s.errorf("%s overflows int", constant.Text)
|
||||||
|
}
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexConstant(s string) bool {
|
||||||
|
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(field)
|
||||||
|
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(chain)
|
||||||
|
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||||
|
pipe := s.evalArg(dot, nil, chain.Node)
|
||||||
|
if len(chain.Field) == 0 {
|
||||||
|
s.errorf("internal error: no fields in evalChainNode")
|
||||||
|
}
|
||||||
|
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||||
|
s.at(variable)
|
||||||
|
value := s.varValue(variable.Ident[0])
|
||||||
|
if len(variable.Ident) == 1 {
|
||||||
|
s.notAFunction(args, final)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||||
|
// dot is the environment in which to evaluate arguments, while
|
||||||
|
// receiver is the value being walked along the chain.
|
||||||
|
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
n := len(ident)
|
||||||
|
for i := 0; i < n-1; i++ {
|
||||||
|
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
||||||
|
}
|
||||||
|
// Now if it's a method, it gets the arguments.
|
||||||
|
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(node)
|
||||||
|
name := node.Ident
|
||||||
|
function, ok := findFunction(name, s.tmpl)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("%q is not a defined function", name)
|
||||||
|
}
|
||||||
|
return s.evalCall(dot, function, cmd, name, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||||
|
// The 'final' argument represents the return value from the preceding
|
||||||
|
// value of the pipeline, if any.
|
||||||
|
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||||
|
if !receiver.IsValid() {
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
typ := receiver.Type()
|
||||||
|
receiver, _ = indirect(receiver)
|
||||||
|
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||||
|
// we see all methods of T and *T.
|
||||||
|
ptr := receiver
|
||||||
|
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
||||||
|
ptr = ptr.Addr()
|
||||||
|
}
|
||||||
|
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||||
|
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||||
|
}
|
||||||
|
hasArgs := len(args) > 1 || final.IsValid()
|
||||||
|
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
||||||
|
receiver, isNil := indirect(receiver)
|
||||||
|
if isNil {
|
||||||
|
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||||
|
}
|
||||||
|
switch receiver.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||||
|
if ok {
|
||||||
|
field := receiver.FieldByIndex(tField.Index)
|
||||||
|
if tField.PkgPath != "" { // field is unexported
|
||||||
|
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||||
|
}
|
||||||
|
// If it's a function, we must call it.
|
||||||
|
if hasArgs {
|
||||||
|
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
// If it's a map, attempt to use the field name as a key.
|
||||||
|
nameVal := reflect.ValueOf(fieldName)
|
||||||
|
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||||
|
if hasArgs {
|
||||||
|
s.errorf("%s is not a method but has arguments", fieldName)
|
||||||
|
}
|
||||||
|
return receiver.MapIndex(nameVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||||
|
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||||
|
// as the function itself.
|
||||||
|
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
if args != nil {
|
||||||
|
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||||
|
}
|
||||||
|
typ := fun.Type()
|
||||||
|
numIn := len(args)
|
||||||
|
if final.IsValid() {
|
||||||
|
numIn++
|
||||||
|
}
|
||||||
|
numFixed := len(args)
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||||
|
if numIn < numFixed {
|
||||||
|
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||||
|
}
|
||||||
|
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||||
|
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||||
|
}
|
||||||
|
if !goodFunc(typ) {
|
||||||
|
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||||
|
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||||
|
}
|
||||||
|
// Build the arg list.
|
||||||
|
argv := make([]reflect.Value, numIn)
|
||||||
|
// Args must be evaluated. Fixed args first.
|
||||||
|
i := 0
|
||||||
|
for ; i < numFixed && i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||||
|
}
|
||||||
|
// Now the ... args.
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||||
|
for ; i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(dot, argType, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add final value if necessary.
|
||||||
|
if final.IsValid() {
|
||||||
|
t := typ.In(typ.NumIn() - 1)
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
argv[i] = s.validateType(final, t)
|
||||||
|
}
|
||||||
|
result := fun.Call(argv)
|
||||||
|
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||||
|
if len(result) == 2 && !result[1].IsNil() {
|
||||||
|
s.at(node)
|
||||||
|
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||||
|
}
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||||
|
func canBeNil(typ reflect.Type) bool {
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateType guarantees that the value is valid and assignable to the type.
|
||||||
|
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||||
|
if !value.IsValid() {
|
||||||
|
if typ == nil || canBeNil(typ) {
|
||||||
|
// An untyped nil interface{}. Accept as a proper nil value.
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
s.errorf("invalid value; expected %s", typ)
|
||||||
|
}
|
||||||
|
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||||
|
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||||
|
value = value.Elem()
|
||||||
|
if value.Type().AssignableTo(typ) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
}
|
||||||
|
// Does one dereference or indirection work? We could do more, as we
|
||||||
|
// do with method receivers, but that gets messy and method receivers
|
||||||
|
// are much more constrained, so it makes more sense there than here.
|
||||||
|
// Besides, one is almost always all you need.
|
||||||
|
switch {
|
||||||
|
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||||
|
value = value.Elem()
|
||||||
|
if !value.IsValid() {
|
||||||
|
s.errorf("dereference of nil pointer of type %s", typ)
|
||||||
|
}
|
||||||
|
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||||
|
value = value.Addr()
|
||||||
|
default:
|
||||||
|
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
switch arg := n.(type) {
|
||||||
|
case *parse.DotNode:
|
||||||
|
return s.validateType(dot, typ)
|
||||||
|
case *parse.NilNode:
|
||||||
|
if canBeNil(typ) {
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
s.errorf("cannot assign nil to %s", typ)
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
return s.evalFunction(dot, arg, arg, nil, zero)
|
||||||
|
case *parse.ChainNode:
|
||||||
|
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
||||||
|
}
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return s.evalBool(typ, n)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return s.evalComplex(typ, n)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return s.evalFloat(typ, n)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return s.evalInteger(typ, n)
|
||||||
|
case reflect.Interface:
|
||||||
|
if typ.NumMethod() == 0 {
|
||||||
|
return s.evalEmptyInterface(dot, n)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
return s.evalString(typ, n)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return s.evalUnsignedInteger(typ, n)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.BoolNode); ok {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetBool(n.True)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected bool; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.StringNode); ok {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetString(n.Text)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected string; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetInt(n.Int64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected integer; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetUint(n.Uint64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected unsigned integer; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetFloat(n.Float64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected float; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetComplex(n.Complex128)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected complex; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *parse.BoolNode:
|
||||||
|
return reflect.ValueOf(n.True)
|
||||||
|
case *parse.DotNode:
|
||||||
|
return dot
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.evalFieldNode(dot, n, nil, zero)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
return s.evalFunction(dot, n, n, nil, zero)
|
||||||
|
case *parse.NilNode:
|
||||||
|
// NilNode is handled in evalArg, the only place that calls here.
|
||||||
|
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||||
|
case *parse.NumberNode:
|
||||||
|
return s.idealConstant(n)
|
||||||
|
case *parse.StringNode:
|
||||||
|
return reflect.ValueOf(n.Text)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.evalVariableNode(dot, n, nil, zero)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
return s.evalPipeline(dot, n)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||||
|
// We indirect through pointers and empty interfaces (only) because
|
||||||
|
// non-empty interfaces have methods we might need.
|
||||||
|
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||||
|
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||||
|
if v.IsNil() {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printValue writes the textual representation of the value to the output of
|
||||||
|
// the template.
|
||||||
|
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||||
|
s.at(n)
|
||||||
|
iface, ok := printableValue(v)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("can't print %s of type %s", n, v.Type())
|
||||||
|
}
|
||||||
|
fmt.Fprint(s.wr, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printableValue returns the, possibly indirected, interface value inside v that
|
||||||
|
// is best for a call to formatted printer.
|
||||||
|
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||||
|
}
|
||||||
|
if !v.IsValid() {
|
||||||
|
return "<no value>", true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||||
|
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||||
|
v = v.Addr()
|
||||||
|
} else {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.Interface(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types to help sort the keys in a map for reproducible output.
|
||||||
|
|
||||||
|
type rvs []reflect.Value
|
||||||
|
|
||||||
|
func (x rvs) Len() int { return len(x) }
|
||||||
|
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
type rvInts struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
||||||
|
|
||||||
|
type rvUints struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
||||||
|
|
||||||
|
type rvFloats struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
||||||
|
|
||||||
|
type rvStrings struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
||||||
|
|
||||||
|
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||||
|
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||||
|
if len(v) <= 1 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
switch v[0].Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
sort.Sort(rvFloats{v})
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
sort.Sort(rvInts{v})
|
||||||
|
case reflect.String:
|
||||||
|
sort.Sort(rvStrings{v})
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
sort.Sort(rvUints{v})
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
Normal file
@ -0,0 +1,598 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||||
|
// Each function must have either a single return value, or two return values of
|
||||||
|
// which the second has type error. In that case, if the second (error)
|
||||||
|
// return value evaluates to non-nil during execution, execution terminates and
|
||||||
|
// Execute returns that error.
|
||||||
|
type FuncMap map[string]interface{}
|
||||||
|
|
||||||
|
var builtins = FuncMap{
|
||||||
|
"and": and,
|
||||||
|
"call": call,
|
||||||
|
"html": HTMLEscaper,
|
||||||
|
"index": index,
|
||||||
|
"js": JSEscaper,
|
||||||
|
"len": length,
|
||||||
|
"not": not,
|
||||||
|
"or": or,
|
||||||
|
"print": fmt.Sprint,
|
||||||
|
"printf": fmt.Sprintf,
|
||||||
|
"println": fmt.Sprintln,
|
||||||
|
"urlquery": URLQueryEscaper,
|
||||||
|
|
||||||
|
// Comparisons
|
||||||
|
"eq": eq, // ==
|
||||||
|
"ge": ge, // >=
|
||||||
|
"gt": gt, // >
|
||||||
|
"le": le, // <=
|
||||||
|
"lt": lt, // <
|
||||||
|
"ne": ne, // !=
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtinFuncs = createValueFuncs(builtins)
|
||||||
|
|
||||||
|
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||||
|
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||||
|
m := make(map[string]reflect.Value)
|
||||||
|
addValueFuncs(m, funcMap)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||||
|
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||||
|
for name, fn := range in {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
if v.Kind() != reflect.Func {
|
||||||
|
panic("value for " + name + " not a function")
|
||||||
|
}
|
||||||
|
if !goodFunc(v.Type()) {
|
||||||
|
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||||
|
}
|
||||||
|
out[name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||||
|
// call addValueFuncs first.
|
||||||
|
func addFuncs(out, in FuncMap) {
|
||||||
|
for name, fn := range in {
|
||||||
|
out[name] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// goodFunc checks that the function or method has the right result signature.
|
||||||
|
func goodFunc(typ reflect.Type) bool {
|
||||||
|
// We allow functions with 1 result or 2 results where the second is an error.
|
||||||
|
switch {
|
||||||
|
case typ.NumOut() == 1:
|
||||||
|
return true
|
||||||
|
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// findFunction looks for a function in the template, and global map.
|
||||||
|
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||||
|
if tmpl != nil && tmpl.common != nil {
|
||||||
|
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
return reflect.Value{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexing.
|
||||||
|
|
||||||
|
// index returns the result of indexing its first argument by the following
|
||||||
|
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||||
|
// indexed item must be a map, slice, or array.
|
||||||
|
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
||||||
|
v := reflect.ValueOf(item)
|
||||||
|
for _, i := range indices {
|
||||||
|
index := reflect.ValueOf(i)
|
||||||
|
var isNil bool
|
||||||
|
if v, isNil = indirect(v); isNil {
|
||||||
|
return nil, fmt.Errorf("index of nil pointer")
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.String:
|
||||||
|
var x int64
|
||||||
|
switch index.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
x = index.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
x = int64(index.Uint())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||||
|
}
|
||||||
|
if x < 0 || x >= int64(v.Len()) {
|
||||||
|
return nil, fmt.Errorf("index out of range: %d", x)
|
||||||
|
}
|
||||||
|
v = v.Index(int(x))
|
||||||
|
case reflect.Map:
|
||||||
|
if !index.IsValid() {
|
||||||
|
index = reflect.Zero(v.Type().Key())
|
||||||
|
}
|
||||||
|
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||||
|
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
||||||
|
}
|
||||||
|
if x := v.MapIndex(index); x.IsValid() {
|
||||||
|
v = x
|
||||||
|
} else {
|
||||||
|
v = reflect.Zero(v.Type().Elem())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length
|
||||||
|
|
||||||
|
// length returns the length of the item, with an error if it has no defined length.
|
||||||
|
func length(item interface{}) (int, error) {
|
||||||
|
v, isNil := indirect(reflect.ValueOf(item))
|
||||||
|
if isNil {
|
||||||
|
return 0, fmt.Errorf("len of nil pointer")
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function invocation
|
||||||
|
|
||||||
|
// call returns the result of evaluating the first argument as a function.
|
||||||
|
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||||
|
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
typ := v.Type()
|
||||||
|
if typ.Kind() != reflect.Func {
|
||||||
|
return nil, fmt.Errorf("non-function of type %s", typ)
|
||||||
|
}
|
||||||
|
if !goodFunc(typ) {
|
||||||
|
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||||
|
}
|
||||||
|
numIn := typ.NumIn()
|
||||||
|
var dddType reflect.Type
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
if len(args) < numIn-1 {
|
||||||
|
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||||
|
}
|
||||||
|
dddType = typ.In(numIn - 1).Elem()
|
||||||
|
} else {
|
||||||
|
if len(args) != numIn {
|
||||||
|
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argv := make([]reflect.Value, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
value := reflect.ValueOf(arg)
|
||||||
|
// Compute the expected type. Clumsy because of variadics.
|
||||||
|
var argType reflect.Type
|
||||||
|
if !typ.IsVariadic() || i < numIn-1 {
|
||||||
|
argType = typ.In(i)
|
||||||
|
} else {
|
||||||
|
argType = dddType
|
||||||
|
}
|
||||||
|
if !value.IsValid() && canBeNil(argType) {
|
||||||
|
value = reflect.Zero(argType)
|
||||||
|
}
|
||||||
|
if !value.Type().AssignableTo(argType) {
|
||||||
|
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
||||||
|
}
|
||||||
|
argv[i] = value
|
||||||
|
}
|
||||||
|
result := v.Call(argv)
|
||||||
|
if len(result) == 2 && !result[1].IsNil() {
|
||||||
|
return result[0].Interface(), result[1].Interface().(error)
|
||||||
|
}
|
||||||
|
return result[0].Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean logic.
|
||||||
|
|
||||||
|
func truth(a interface{}) bool {
|
||||||
|
t, _ := isTrue(reflect.ValueOf(a))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// and computes the Boolean AND of its arguments, returning
|
||||||
|
// the first false argument it encounters, or the last argument.
|
||||||
|
func and(arg0 interface{}, args ...interface{}) interface{} {
|
||||||
|
if !truth(arg0) {
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
arg0 = args[i]
|
||||||
|
if !truth(arg0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
|
||||||
|
// or computes the Boolean OR of its arguments, returning
|
||||||
|
// the first true argument it encounters, or the last argument.
|
||||||
|
func or(arg0 interface{}, args ...interface{}) interface{} {
|
||||||
|
if truth(arg0) {
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
arg0 = args[i]
|
||||||
|
if truth(arg0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
|
||||||
|
// not returns the Boolean negation of its argument.
|
||||||
|
func not(arg interface{}) (truth bool) {
|
||||||
|
truth, _ = isTrue(reflect.ValueOf(arg))
|
||||||
|
return !truth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison.
|
||||||
|
|
||||||
|
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadComparisonType = errors.New("invalid type for comparison")
|
||||||
|
errBadComparison = errors.New("incompatible types for comparison")
|
||||||
|
errNoComparison = errors.New("missing argument for comparison")
|
||||||
|
)
|
||||||
|
|
||||||
|
type kind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
invalidKind kind = iota
|
||||||
|
boolKind
|
||||||
|
complexKind
|
||||||
|
intKind
|
||||||
|
floatKind
|
||||||
|
integerKind
|
||||||
|
stringKind
|
||||||
|
uintKind
|
||||||
|
)
|
||||||
|
|
||||||
|
func basicKind(v reflect.Value) (kind, error) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return boolKind, nil
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return intKind, nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return uintKind, nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return floatKind, nil
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return complexKind, nil
|
||||||
|
case reflect.String:
|
||||||
|
return stringKind, nil
|
||||||
|
}
|
||||||
|
return invalidKind, errBadComparisonType
|
||||||
|
}
|
||||||
|
|
||||||
|
// eq evaluates the comparison a == b || a == c || ...
|
||||||
|
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||||
|
v1 := reflect.ValueOf(arg1)
|
||||||
|
k1, err := basicKind(v1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(arg2) == 0 {
|
||||||
|
return false, errNoComparison
|
||||||
|
}
|
||||||
|
for _, arg := range arg2 {
|
||||||
|
v2 := reflect.ValueOf(arg)
|
||||||
|
k2, err := basicKind(v2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
truth := false
|
||||||
|
if k1 != k2 {
|
||||||
|
// Special case: Can compare integer values regardless of type's sign.
|
||||||
|
switch {
|
||||||
|
case k1 == intKind && k2 == uintKind:
|
||||||
|
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||||
|
case k1 == uintKind && k2 == intKind:
|
||||||
|
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||||
|
default:
|
||||||
|
return false, errBadComparison
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch k1 {
|
||||||
|
case boolKind:
|
||||||
|
truth = v1.Bool() == v2.Bool()
|
||||||
|
case complexKind:
|
||||||
|
truth = v1.Complex() == v2.Complex()
|
||||||
|
case floatKind:
|
||||||
|
truth = v1.Float() == v2.Float()
|
||||||
|
case intKind:
|
||||||
|
truth = v1.Int() == v2.Int()
|
||||||
|
case stringKind:
|
||||||
|
truth = v1.String() == v2.String()
|
||||||
|
case uintKind:
|
||||||
|
truth = v1.Uint() == v2.Uint()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if truth {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ne evaluates the comparison a != b.
|
||||||
|
func ne(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// != is the inverse of ==.
|
||||||
|
equal, err := eq(arg1, arg2)
|
||||||
|
return !equal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// lt evaluates the comparison a < b.
|
||||||
|
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
v1 := reflect.ValueOf(arg1)
|
||||||
|
k1, err := basicKind(v1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
v2 := reflect.ValueOf(arg2)
|
||||||
|
k2, err := basicKind(v2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
truth := false
|
||||||
|
if k1 != k2 {
|
||||||
|
// Special case: Can compare integer values regardless of type's sign.
|
||||||
|
switch {
|
||||||
|
case k1 == intKind && k2 == uintKind:
|
||||||
|
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||||
|
case k1 == uintKind && k2 == intKind:
|
||||||
|
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||||
|
default:
|
||||||
|
return false, errBadComparison
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch k1 {
|
||||||
|
case boolKind, complexKind:
|
||||||
|
return false, errBadComparisonType
|
||||||
|
case floatKind:
|
||||||
|
truth = v1.Float() < v2.Float()
|
||||||
|
case intKind:
|
||||||
|
truth = v1.Int() < v2.Int()
|
||||||
|
case stringKind:
|
||||||
|
truth = v1.String() < v2.String()
|
||||||
|
case uintKind:
|
||||||
|
truth = v1.Uint() < v2.Uint()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return truth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// le evaluates the comparison <= b.
|
||||||
|
func le(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// <= is < or ==.
|
||||||
|
lessThan, err := lt(arg1, arg2)
|
||||||
|
if lessThan || err != nil {
|
||||||
|
return lessThan, err
|
||||||
|
}
|
||||||
|
return eq(arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gt evaluates the comparison a > b.
|
||||||
|
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// > is the inverse of <=.
|
||||||
|
lessOrEqual, err := le(arg1, arg2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !lessOrEqual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ge evaluates the comparison a >= b.
|
||||||
|
func ge(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// >= is the inverse of <.
|
||||||
|
lessThan, err := lt(arg1, arg2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !lessThan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlQuot = []byte(""") // shorter than """
|
||||||
|
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||||
|
htmlAmp = []byte("&")
|
||||||
|
htmlLt = []byte("<")
|
||||||
|
htmlGt = []byte(">")
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||||
|
func HTMLEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i, c := range b {
|
||||||
|
var html []byte
|
||||||
|
switch c {
|
||||||
|
case '"':
|
||||||
|
html = htmlQuot
|
||||||
|
case '\'':
|
||||||
|
html = htmlApos
|
||||||
|
case '&':
|
||||||
|
html = htmlAmp
|
||||||
|
case '<':
|
||||||
|
html = htmlLt
|
||||||
|
case '>':
|
||||||
|
html = htmlGt
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
w.Write(html)
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||||
|
func HTMLEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexAny(s, `'"&<>`) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
HTMLEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func HTMLEscaper(args ...interface{}) string {
|
||||||
|
return HTMLEscapeString(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// JavaScript escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsLowUni = []byte(`\u00`)
|
||||||
|
hex = []byte("0123456789ABCDEF")
|
||||||
|
|
||||||
|
jsBackslash = []byte(`\\`)
|
||||||
|
jsApos = []byte(`\'`)
|
||||||
|
jsQuot = []byte(`\"`)
|
||||||
|
jsLt = []byte(`\x3C`)
|
||||||
|
jsGt = []byte(`\x3E`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||||
|
func JSEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
c := b[i]
|
||||||
|
|
||||||
|
if !jsIsSpecial(rune(c)) {
|
||||||
|
// fast path: nothing to do
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
// Quotes, slashes and angle brackets get quoted.
|
||||||
|
// Control characters get written as \u00XX.
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
w.Write(jsBackslash)
|
||||||
|
case '\'':
|
||||||
|
w.Write(jsApos)
|
||||||
|
case '"':
|
||||||
|
w.Write(jsQuot)
|
||||||
|
case '<':
|
||||||
|
w.Write(jsLt)
|
||||||
|
case '>':
|
||||||
|
w.Write(jsGt)
|
||||||
|
default:
|
||||||
|
w.Write(jsLowUni)
|
||||||
|
t, b := c>>4, c&0x0f
|
||||||
|
w.Write(hex[t : t+1])
|
||||||
|
w.Write(hex[b : b+1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unicode rune.
|
||||||
|
r, size := utf8.DecodeRune(b[i:])
|
||||||
|
if unicode.IsPrint(r) {
|
||||||
|
w.Write(b[i : i+size])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "\\u%04X", r)
|
||||||
|
}
|
||||||
|
i += size - 1
|
||||||
|
}
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||||
|
func JSEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
JSEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsIsSpecial(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '\\', '\'', '"', '<', '>':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return r < ' ' || utf8.RuneSelf <= r
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func JSEscaper(args ...interface{}) string {
|
||||||
|
return JSEscapeString(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||||
|
// its arguments in a form suitable for embedding in a URL query.
|
||||||
|
func URLQueryEscaper(args ...interface{}) string {
|
||||||
|
return url.QueryEscape(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||||
|
// fmt.Sprint(args...)
|
||||||
|
// except that each argument is indirected (if a pointer), as required,
|
||||||
|
// using the same rules as the default string evaluation during template
|
||||||
|
// execution.
|
||||||
|
func evalArgs(args []interface{}) string {
|
||||||
|
ok := false
|
||||||
|
var s string
|
||||||
|
// Fast path for simple common case.
|
||||||
|
if len(args) == 1 {
|
||||||
|
s, ok = args[0].(string)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
for i, arg := range args {
|
||||||
|
a, ok := printableValue(reflect.ValueOf(arg))
|
||||||
|
if ok {
|
||||||
|
args[i] = a
|
||||||
|
} // else left fmt do its thing
|
||||||
|
}
|
||||||
|
s = fmt.Sprint(args...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Helper functions to make constructing templates easier.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Functions and methods to parse templates.
|
||||||
|
|
||||||
|
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||||
|
// and panics if the error is non-nil. It is intended for use in variable
|
||||||
|
// initializations such as
|
||||||
|
// var t = template.Must(template.New("name").Parse("text"))
|
||||||
|
func Must(t *Template, err error) *Template {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles creates a new Template and parses the template definitions from
|
||||||
|
// the named files. The returned template's name will have the (base) name and
|
||||||
|
// (parsed) contents of the first file. There must be at least one file.
|
||||||
|
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||||
|
func ParseFiles(filenames ...string) (*Template, error) {
|
||||||
|
return parseFiles(nil, filenames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles parses the named files and associates the resulting templates with
|
||||||
|
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||||
|
// otherwise it is t. There must be at least one file.
|
||||||
|
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||||
|
return parseFiles(t, filenames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFiles is the helper for the method and function. If the argument
|
||||||
|
// template is nil, it is created from the first file.
|
||||||
|
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
// Not really a problem, but be consistent.
|
||||||
|
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||||
|
}
|
||||||
|
for _, filename := range filenames {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
name := filepath.Base(filename)
|
||||||
|
// First template becomes return value if not already defined,
|
||||||
|
// and we use that one for subsequent New calls to associate
|
||||||
|
// all the templates together. Also, if this file has the same name
|
||||||
|
// as t, this file becomes the contents of t, so
|
||||||
|
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||||
|
// works. Otherwise we create a new template associated with t.
|
||||||
|
var tmpl *Template
|
||||||
|
if t == nil {
|
||||||
|
t = New(name)
|
||||||
|
}
|
||||||
|
if name == t.Name() {
|
||||||
|
tmpl = t
|
||||||
|
} else {
|
||||||
|
tmpl = t.New(name)
|
||||||
|
}
|
||||||
|
_, err = tmpl.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGlob creates a new Template and parses the template definitions from the
|
||||||
|
// files identified by the pattern, which must match at least one file. The
|
||||||
|
// returned template will have the (base) name and (parsed) contents of the
|
||||||
|
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||||
|
// ParseFiles with the list of files matched by the pattern.
|
||||||
|
func ParseGlob(pattern string) (*Template, error) {
|
||||||
|
return parseGlob(nil, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGlob parses the template definitions in the files identified by the
|
||||||
|
// pattern and associates the resulting templates with t. The pattern is
|
||||||
|
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||||
|
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||||
|
// pattern.
|
||||||
|
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||||
|
return parseGlob(t, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGlob is the implementation of the function and method ParseGlob.
|
||||||
|
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||||
|
filenames, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||||
|
}
|
||||||
|
return parseFiles(t, filenames...)
|
||||||
|
}
|
||||||
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// item represents a token or text string returned from the scanner.
|
||||||
|
type item struct {
|
||||||
|
typ itemType // The type of this item.
|
||||||
|
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||||
|
val string // The value of this item.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i item) String() string {
|
||||||
|
switch {
|
||||||
|
case i.typ == itemEOF:
|
||||||
|
return "EOF"
|
||||||
|
case i.typ == itemError:
|
||||||
|
return i.val
|
||||||
|
case i.typ > itemKeyword:
|
||||||
|
return fmt.Sprintf("<%s>", i.val)
|
||||||
|
case len(i.val) > 10:
|
||||||
|
return fmt.Sprintf("%.10q...", i.val)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", i.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemType identifies the type of lex items.
|
||||||
|
type itemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
itemError itemType = iota // error occurred; value is text of error
|
||||||
|
itemBool // boolean constant
|
||||||
|
itemChar // printable ASCII character; grab bag for comma etc.
|
||||||
|
itemCharConstant // character constant
|
||||||
|
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||||
|
itemColonEquals // colon-equals (':=') introducing a declaration
|
||||||
|
itemEOF
|
||||||
|
itemField // alphanumeric identifier starting with '.'
|
||||||
|
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||||
|
itemLeftDelim // left action delimiter
|
||||||
|
itemLeftParen // '(' inside action
|
||||||
|
itemNumber // simple number, including imaginary
|
||||||
|
itemPipe // pipe symbol
|
||||||
|
itemRawString // raw quoted string (includes quotes)
|
||||||
|
itemRightDelim // right action delimiter
|
||||||
|
itemElideNewline // elide newline after right delim
|
||||||
|
itemRightParen // ')' inside action
|
||||||
|
itemSpace // run of spaces separating arguments
|
||||||
|
itemString // quoted string (includes quotes)
|
||||||
|
itemText // plain text
|
||||||
|
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||||
|
// Keywords appear after all the rest.
|
||||||
|
itemKeyword // used only to delimit the keywords
|
||||||
|
itemDot // the cursor, spelled '.'
|
||||||
|
itemDefine // define keyword
|
||||||
|
itemElse // else keyword
|
||||||
|
itemEnd // end keyword
|
||||||
|
itemIf // if keyword
|
||||||
|
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||||
|
itemRange // range keyword
|
||||||
|
itemTemplate // template keyword
|
||||||
|
itemWith // with keyword
|
||||||
|
)
|
||||||
|
|
||||||
|
var key = map[string]itemType{
|
||||||
|
".": itemDot,
|
||||||
|
"define": itemDefine,
|
||||||
|
"else": itemElse,
|
||||||
|
"end": itemEnd,
|
||||||
|
"if": itemIf,
|
||||||
|
"range": itemRange,
|
||||||
|
"nil": itemNil,
|
||||||
|
"template": itemTemplate,
|
||||||
|
"with": itemWith,
|
||||||
|
}
|
||||||
|
|
||||||
|
const eof = -1
|
||||||
|
|
||||||
|
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||||
|
type stateFn func(*lexer) stateFn
|
||||||
|
|
||||||
|
// lexer holds the state of the scanner.
|
||||||
|
type lexer struct {
|
||||||
|
name string // the name of the input; used only for error reports
|
||||||
|
input string // the string being scanned
|
||||||
|
leftDelim string // start of action
|
||||||
|
rightDelim string // end of action
|
||||||
|
state stateFn // the next lexing function to enter
|
||||||
|
pos Pos // current position in the input
|
||||||
|
start Pos // start position of this item
|
||||||
|
width Pos // width of last rune read from input
|
||||||
|
lastPos Pos // position of most recent item returned by nextItem
|
||||||
|
items chan item // channel of scanned items
|
||||||
|
parenDepth int // nesting depth of ( ) exprs
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next rune in the input.
|
||||||
|
func (l *lexer) next() rune {
|
||||||
|
if int(l.pos) >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.width = Pos(w)
|
||||||
|
l.pos += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next rune in the input.
|
||||||
|
func (l *lexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one rune. Can only be called once per call of next.
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emit(t itemType) {
|
||||||
|
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore skips over the pending input before this point.
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept consumes the next rune if it's from the valid set.
|
||||||
|
func (l *lexer) accept(valid string) bool {
|
||||||
|
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptRun consumes a run of runes from the valid set.
|
||||||
|
func (l *lexer) acceptRun(valid string) {
|
||||||
|
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lineNumber reports which line we're on, based on the position of
|
||||||
|
// the previous item returned by nextItem. Doing it this way
|
||||||
|
// means we don't have to worry about peek double counting.
|
||||||
|
func (l *lexer) lineNumber() int {
|
||||||
|
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf returns an error token and terminates the scan by passing
|
||||||
|
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||||
|
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||||
|
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextItem returns the next item from the input.
|
||||||
|
func (l *lexer) nextItem() item {
|
||||||
|
item := <-l.items
|
||||||
|
l.lastPos = item.pos
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// lex creates a new scanner for the input string.
|
||||||
|
func lex(name, input, left, right string) *lexer {
|
||||||
|
if left == "" {
|
||||||
|
left = leftDelim
|
||||||
|
}
|
||||||
|
if right == "" {
|
||||||
|
right = rightDelim
|
||||||
|
}
|
||||||
|
l := &lexer{
|
||||||
|
name: name,
|
||||||
|
input: input,
|
||||||
|
leftDelim: left,
|
||||||
|
rightDelim: right,
|
||||||
|
items: make(chan item),
|
||||||
|
}
|
||||||
|
go l.run()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// run runs the state machine for the lexer.
|
||||||
|
func (l *lexer) run() {
|
||||||
|
for l.state = lexText; l.state != nil; {
|
||||||
|
l.state = l.state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// state functions
|
||||||
|
|
||||||
|
const (
|
||||||
|
leftDelim = "{{"
|
||||||
|
rightDelim = "}}"
|
||||||
|
leftComment = "/*"
|
||||||
|
rightComment = "*/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lexText scans until an opening action delimiter, "{{".
|
||||||
|
func lexText(l *lexer) stateFn {
|
||||||
|
for {
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(itemText)
|
||||||
|
}
|
||||||
|
return lexLeftDelim
|
||||||
|
}
|
||||||
|
if l.next() == eof {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Correctly reached EOF.
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(itemText)
|
||||||
|
}
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexLeftDelim scans the left delimiter, which is known to be present.
|
||||||
|
func lexLeftDelim(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(l.leftDelim))
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
||||||
|
return lexComment
|
||||||
|
}
|
||||||
|
l.emit(itemLeftDelim)
|
||||||
|
l.parenDepth = 0
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexComment scans a comment. The left comment marker is known to be present.
|
||||||
|
func lexComment(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(leftComment))
|
||||||
|
i := strings.Index(l.input[l.pos:], rightComment)
|
||||||
|
if i < 0 {
|
||||||
|
return l.errorf("unclosed comment")
|
||||||
|
}
|
||||||
|
l.pos += Pos(i + len(rightComment))
|
||||||
|
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||||
|
return l.errorf("comment ends before closing delimiter")
|
||||||
|
|
||||||
|
}
|
||||||
|
l.pos += Pos(len(l.rightDelim))
|
||||||
|
l.ignore()
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRightDelim scans the right delimiter, which is known to be present.
|
||||||
|
func lexRightDelim(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(l.rightDelim))
|
||||||
|
l.emit(itemRightDelim)
|
||||||
|
if l.peek() == '\\' {
|
||||||
|
l.pos++
|
||||||
|
l.emit(itemElideNewline)
|
||||||
|
}
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexInsideAction scans the elements inside action delimiters.
|
||||||
|
func lexInsideAction(l *lexer) stateFn {
|
||||||
|
// Either number, quoted string, or identifier.
|
||||||
|
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||||
|
// Pipe symbols separate and are emitted.
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||||
|
if l.parenDepth == 0 {
|
||||||
|
return lexRightDelim
|
||||||
|
}
|
||||||
|
return l.errorf("unclosed left paren")
|
||||||
|
}
|
||||||
|
switch r := l.next(); {
|
||||||
|
case r == eof || isEndOfLine(r):
|
||||||
|
return l.errorf("unclosed action")
|
||||||
|
case isSpace(r):
|
||||||
|
return lexSpace
|
||||||
|
case r == ':':
|
||||||
|
if l.next() != '=' {
|
||||||
|
return l.errorf("expected :=")
|
||||||
|
}
|
||||||
|
l.emit(itemColonEquals)
|
||||||
|
case r == '|':
|
||||||
|
l.emit(itemPipe)
|
||||||
|
case r == '"':
|
||||||
|
return lexQuote
|
||||||
|
case r == '`':
|
||||||
|
return lexRawQuote
|
||||||
|
case r == '$':
|
||||||
|
return lexVariable
|
||||||
|
case r == '\'':
|
||||||
|
return lexChar
|
||||||
|
case r == '.':
|
||||||
|
// special look-ahead for ".field" so we don't break l.backup().
|
||||||
|
if l.pos < Pos(len(l.input)) {
|
||||||
|
r := l.input[l.pos]
|
||||||
|
if r < '0' || '9' < r {
|
||||||
|
return lexField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallthrough // '.' can start a number.
|
||||||
|
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||||
|
l.backup()
|
||||||
|
return lexNumber
|
||||||
|
case isAlphaNumeric(r):
|
||||||
|
l.backup()
|
||||||
|
return lexIdentifier
|
||||||
|
case r == '(':
|
||||||
|
l.emit(itemLeftParen)
|
||||||
|
l.parenDepth++
|
||||||
|
return lexInsideAction
|
||||||
|
case r == ')':
|
||||||
|
l.emit(itemRightParen)
|
||||||
|
l.parenDepth--
|
||||||
|
if l.parenDepth < 0 {
|
||||||
|
return l.errorf("unexpected right paren %#U", r)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||||
|
l.emit(itemChar)
|
||||||
|
return lexInsideAction
|
||||||
|
default:
|
||||||
|
return l.errorf("unrecognized character in action: %#U", r)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexSpace scans a run of space characters.
|
||||||
|
// One space has already been seen.
|
||||||
|
func lexSpace(l *lexer) stateFn {
|
||||||
|
for isSpace(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
}
|
||||||
|
l.emit(itemSpace)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexIdentifier scans an alphanumeric.
|
||||||
|
func lexIdentifier(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch r := l.next(); {
|
||||||
|
case isAlphaNumeric(r):
|
||||||
|
// absorb.
|
||||||
|
default:
|
||||||
|
l.backup()
|
||||||
|
word := l.input[l.start:l.pos]
|
||||||
|
if !l.atTerminator() {
|
||||||
|
return l.errorf("bad character %#U", r)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case key[word] > itemKeyword:
|
||||||
|
l.emit(key[word])
|
||||||
|
case word[0] == '.':
|
||||||
|
l.emit(itemField)
|
||||||
|
case word == "true", word == "false":
|
||||||
|
l.emit(itemBool)
|
||||||
|
default:
|
||||||
|
l.emit(itemIdentifier)
|
||||||
|
}
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexField scans a field: .Alphanumeric.
|
||||||
|
// The . has been scanned.
|
||||||
|
func lexField(l *lexer) stateFn {
|
||||||
|
return lexFieldOrVariable(l, itemField)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexVariable scans a Variable: $Alphanumeric.
|
||||||
|
// The $ has been scanned.
|
||||||
|
func lexVariable(l *lexer) stateFn {
|
||||||
|
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||||
|
l.emit(itemVariable)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
return lexFieldOrVariable(l, itemVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||||
|
// The . or $ has been scanned.
|
||||||
|
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||||
|
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||||
|
if typ == itemVariable {
|
||||||
|
l.emit(itemVariable)
|
||||||
|
} else {
|
||||||
|
l.emit(itemDot)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
var r rune
|
||||||
|
for {
|
||||||
|
r = l.next()
|
||||||
|
if !isAlphaNumeric(r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !l.atTerminator() {
|
||||||
|
return l.errorf("bad character %#U", r)
|
||||||
|
}
|
||||||
|
l.emit(typ)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// atTerminator reports whether the input is at valid termination character to
|
||||||
|
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||||
|
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||||
|
// day to implement arithmetic.
|
||||||
|
func (l *lexer) atTerminator() bool {
|
||||||
|
r := l.peek()
|
||||||
|
if isSpace(r) || isEndOfLine(r) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case eof, '.', ',', '|', ':', ')', '(':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||||
|
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||||
|
// bad choice of delimiter.
|
||||||
|
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexChar scans a character constant. The initial quote is already
|
||||||
|
// scanned. Syntax checking is done by the parser.
|
||||||
|
func lexChar(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != eof && r != '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated character constant")
|
||||||
|
case '\'':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemCharConstant)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||||
|
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||||
|
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||||
|
// strconv) will notice.
|
||||||
|
func lexNumber(l *lexer) stateFn {
|
||||||
|
if !l.scanNumber() {
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||||
|
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||||
|
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(itemComplex)
|
||||||
|
} else {
|
||||||
|
l.emit(itemNumber)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) scanNumber() bool {
|
||||||
|
// Optional leading sign.
|
||||||
|
l.accept("+-")
|
||||||
|
// Is it hex?
|
||||||
|
digits := "0123456789"
|
||||||
|
if l.accept("0") && l.accept("xX") {
|
||||||
|
digits = "0123456789abcdefABCDEF"
|
||||||
|
}
|
||||||
|
l.acceptRun(digits)
|
||||||
|
if l.accept(".") {
|
||||||
|
l.acceptRun(digits)
|
||||||
|
}
|
||||||
|
if l.accept("eE") {
|
||||||
|
l.accept("+-")
|
||||||
|
l.acceptRun("0123456789")
|
||||||
|
}
|
||||||
|
// Is it imaginary?
|
||||||
|
l.accept("i")
|
||||||
|
// Next thing mustn't be alphanumeric.
|
||||||
|
if isAlphaNumeric(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexQuote scans a quoted string.
|
||||||
|
func lexQuote(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != eof && r != '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated quoted string")
|
||||||
|
case '"':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemString)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRawQuote scans a raw quoted string.
|
||||||
|
func lexRawQuote(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated raw quoted string")
|
||||||
|
case '`':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemRawString)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpace reports whether r is a space character.
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
return r == ' ' || r == '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEndOfLine reports whether r is an end-of-line character.
|
||||||
|
func isEndOfLine(r rune) bool {
|
||||||
|
return r == '\r' || r == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||||
|
func isAlphaNumeric(r rune) bool {
|
||||||
|
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||||
|
}
|
||||||
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
Normal file
@ -0,0 +1,834 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Parse nodes.
|
||||||
|
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||||
|
|
||||||
|
// A Node is an element in the parse tree. The interface is trivial.
|
||||||
|
// The interface contains an unexported method so that only
|
||||||
|
// types local to this package can satisfy it.
|
||||||
|
type Node interface {
|
||||||
|
Type() NodeType
|
||||||
|
String() string
|
||||||
|
// Copy does a deep copy of the Node and all its components.
|
||||||
|
// To avoid type assertions, some XxxNodes also have specialized
|
||||||
|
// CopyXxx methods that return *XxxNode.
|
||||||
|
Copy() Node
|
||||||
|
Position() Pos // byte position of start of node in full original input string
|
||||||
|
// tree returns the containing *Tree.
|
||||||
|
// It is unexported so all implementations of Node are in this package.
|
||||||
|
tree() *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeType identifies the type of a parse tree node.
|
||||||
|
type NodeType int
|
||||||
|
|
||||||
|
// Pos represents a byte position in the original input text from which
|
||||||
|
// this template was parsed.
|
||||||
|
type Pos int
|
||||||
|
|
||||||
|
func (p Pos) Position() Pos {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns itself and provides an easy default implementation
|
||||||
|
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||||
|
func (t NodeType) Type() NodeType {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NodeText NodeType = iota // Plain text.
|
||||||
|
NodeAction // A non-control action such as a field evaluation.
|
||||||
|
NodeBool // A boolean constant.
|
||||||
|
NodeChain // A sequence of field accesses.
|
||||||
|
NodeCommand // An element of a pipeline.
|
||||||
|
NodeDot // The cursor, dot.
|
||||||
|
nodeElse // An else action. Not added to tree.
|
||||||
|
nodeEnd // An end action. Not added to tree.
|
||||||
|
NodeField // A field or method name.
|
||||||
|
NodeIdentifier // An identifier; always a function name.
|
||||||
|
NodeIf // An if action.
|
||||||
|
NodeList // A list of Nodes.
|
||||||
|
NodeNil // An untyped nil constant.
|
||||||
|
NodeNumber // A numerical constant.
|
||||||
|
NodePipe // A pipeline of commands.
|
||||||
|
NodeRange // A range action.
|
||||||
|
NodeString // A string constant.
|
||||||
|
NodeTemplate // A template invocation action.
|
||||||
|
NodeVariable // A $ variable.
|
||||||
|
NodeWith // A with action.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nodes.
|
||||||
|
|
||||||
|
// ListNode holds a sequence of nodes.
|
||||||
|
type ListNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Nodes []Node // The element nodes in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newList(pos Pos) *ListNode {
|
||||||
|
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) append(n Node) {
|
||||||
|
l.Nodes = append(l.Nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) tree() *Tree {
|
||||||
|
return l.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) String() string {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
for _, n := range l.Nodes {
|
||||||
|
fmt.Fprint(b, n)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) CopyList() *ListNode {
|
||||||
|
if l == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
n := l.tr.newList(l.Pos)
|
||||||
|
for _, elem := range l.Nodes {
|
||||||
|
n.append(elem.Copy())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) Copy() Node {
|
||||||
|
return l.CopyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextNode holds plain text.
|
||||||
|
type TextNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Text []byte // The text; may span newlines.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||||
|
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) String() string {
|
||||||
|
return fmt.Sprintf(textFormat, t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) tree() *Tree {
|
||||||
|
return t.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) Copy() Node {
|
||||||
|
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeNode holds a pipeline with optional declaration
|
||||||
|
type PipeNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Decl []*VariableNode // Variable declarations in lexical order.
|
||||||
|
Cmds []*CommandNode // The commands in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
||||||
|
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) append(command *CommandNode) {
|
||||||
|
p.Cmds = append(p.Cmds, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) String() string {
|
||||||
|
s := ""
|
||||||
|
if len(p.Decl) > 0 {
|
||||||
|
for i, v := range p.Decl {
|
||||||
|
if i > 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += v.String()
|
||||||
|
}
|
||||||
|
s += " := "
|
||||||
|
}
|
||||||
|
for i, c := range p.Cmds {
|
||||||
|
if i > 0 {
|
||||||
|
s += " | "
|
||||||
|
}
|
||||||
|
s += c.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) tree() *Tree {
|
||||||
|
return p.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||||
|
if p == nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
var decl []*VariableNode
|
||||||
|
for _, d := range p.Decl {
|
||||||
|
decl = append(decl, d.Copy().(*VariableNode))
|
||||||
|
}
|
||||||
|
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
||||||
|
for _, c := range p.Cmds {
|
||||||
|
n.append(c.Copy().(*CommandNode))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) Copy() Node {
|
||||||
|
return p.CopyPipe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionNode holds an action (something bounded by delimiters).
|
||||||
|
// Control actions have their own nodes; ActionNode represents simple
|
||||||
|
// ones such as field evaluations and parenthesized pipelines.
|
||||||
|
type ActionNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Pipe *PipeNode // The pipeline in the action.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||||
|
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) String() string {
|
||||||
|
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) tree() *Tree {
|
||||||
|
return a.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) Copy() Node {
|
||||||
|
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||||
|
type CommandNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||||
|
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) append(arg Node) {
|
||||||
|
c.Args = append(c.Args, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for i, arg := range c.Args {
|
||||||
|
if i > 0 {
|
||||||
|
s += " "
|
||||||
|
}
|
||||||
|
if arg, ok := arg.(*PipeNode); ok {
|
||||||
|
s += "(" + arg.String() + ")"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s += arg.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) tree() *Tree {
|
||||||
|
return c.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) Copy() Node {
|
||||||
|
if c == nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
n := c.tr.newCommand(c.Pos)
|
||||||
|
for _, c := range c.Args {
|
||||||
|
n.append(c.Copy())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentifierNode holds an identifier.
|
||||||
|
type IdentifierNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident string // The identifier's name.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||||
|
func NewIdentifier(ident string) *IdentifierNode {
|
||||||
|
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||||
|
// Chained for convenience.
|
||||||
|
// TODO: fix one day?
|
||||||
|
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||||
|
i.Pos = pos
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||||
|
// Chained for convenience.
|
||||||
|
// TODO: fix one day?
|
||||||
|
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||||
|
i.tr = t
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) String() string {
|
||||||
|
return i.Ident
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) tree() *Tree {
|
||||||
|
return i.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) Copy() Node {
|
||||||
|
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariableNode holds a list of variable names, possibly with chained field
|
||||||
|
// accesses. The dollar sign is part of the (first) name.
|
||||||
|
type VariableNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident []string // Variable name and fields in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||||
|
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for i, id := range v.Ident {
|
||||||
|
if i > 0 {
|
||||||
|
s += "."
|
||||||
|
}
|
||||||
|
s += id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) tree() *Tree {
|
||||||
|
return v.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) Copy() Node {
|
||||||
|
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DotNode holds the special identifier '.'.
|
||||||
|
type DotNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||||
|
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) Type() NodeType {
|
||||||
|
// Override method on embedded NodeType for API compatibility.
|
||||||
|
// TODO: Not really a problem; could change API without effect but
|
||||||
|
// api tool complains.
|
||||||
|
return NodeDot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) String() string {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) tree() *Tree {
|
||||||
|
return d.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) Copy() Node {
|
||||||
|
return d.tr.newDot(d.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||||
|
type NilNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||||
|
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) Type() NodeType {
|
||||||
|
// Override method on embedded NodeType for API compatibility.
|
||||||
|
// TODO: Not really a problem; could change API without effect but
|
||||||
|
// api tool complains.
|
||||||
|
return NodeNil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) String() string {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) tree() *Tree {
|
||||||
|
return n.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) Copy() Node {
|
||||||
|
return n.tr.newNil(n.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldNode holds a field (identifier starting with '.').
|
||||||
|
// The names may be chained ('.x.y').
|
||||||
|
// The period is dropped from each ident.
|
||||||
|
type FieldNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident []string // The identifiers in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||||
|
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for _, id := range f.Ident {
|
||||||
|
s += "." + id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) tree() *Tree {
|
||||||
|
return f.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) Copy() Node {
|
||||||
|
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||||
|
// The names may be chained ('.x.y').
|
||||||
|
// The periods are dropped from each ident.
|
||||||
|
type ChainNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Node Node
|
||||||
|
Field []string // The identifiers in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||||
|
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||||
|
func (c *ChainNode) Add(field string) {
|
||||||
|
if len(field) == 0 || field[0] != '.' {
|
||||||
|
panic("no dot in field")
|
||||||
|
}
|
||||||
|
field = field[1:] // Remove leading dot.
|
||||||
|
if field == "" {
|
||||||
|
panic("empty field")
|
||||||
|
}
|
||||||
|
c.Field = append(c.Field, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) String() string {
|
||||||
|
s := c.Node.String()
|
||||||
|
if _, ok := c.Node.(*PipeNode); ok {
|
||||||
|
s = "(" + s + ")"
|
||||||
|
}
|
||||||
|
for _, field := range c.Field {
|
||||||
|
s += "." + field
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) tree() *Tree {
|
||||||
|
return c.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) Copy() Node {
|
||||||
|
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolNode holds a boolean constant.
|
||||||
|
type BoolNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
True bool // The value of the boolean constant.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||||
|
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) String() string {
|
||||||
|
if b.True {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) tree() *Tree {
|
||||||
|
return b.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) Copy() Node {
|
||||||
|
return b.tr.newBool(b.Pos, b.True)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||||
|
// The value is parsed and stored under all the types that can represent the value.
|
||||||
|
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||||
|
type NumberNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
IsInt bool // Number has an integral value.
|
||||||
|
IsUint bool // Number has an unsigned integral value.
|
||||||
|
IsFloat bool // Number has a floating-point value.
|
||||||
|
IsComplex bool // Number is complex.
|
||||||
|
Int64 int64 // The signed integer value.
|
||||||
|
Uint64 uint64 // The unsigned integer value.
|
||||||
|
Float64 float64 // The floating-point value.
|
||||||
|
Complex128 complex128 // The complex value.
|
||||||
|
Text string // The original textual representation from the input.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||||
|
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||||
|
switch typ {
|
||||||
|
case itemCharConstant:
|
||||||
|
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tail != "'" {
|
||||||
|
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||||
|
}
|
||||||
|
n.Int64 = int64(rune)
|
||||||
|
n.IsInt = true
|
||||||
|
n.Uint64 = uint64(rune)
|
||||||
|
n.IsUint = true
|
||||||
|
n.Float64 = float64(rune) // odd but those are the rules.
|
||||||
|
n.IsFloat = true
|
||||||
|
return n, nil
|
||||||
|
case itemComplex:
|
||||||
|
// fmt.Sscan can parse the pair, so let it do the work.
|
||||||
|
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.IsComplex = true
|
||||||
|
n.simplifyComplex()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
// Imaginary constants can only be complex unless they are zero.
|
||||||
|
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||||
|
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsComplex = true
|
||||||
|
n.Complex128 = complex(0, f)
|
||||||
|
n.simplifyComplex()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do integer test first so we get 0x123 etc.
|
||||||
|
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||||
|
if err == nil {
|
||||||
|
n.IsUint = true
|
||||||
|
n.Uint64 = u
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseInt(text, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsInt = true
|
||||||
|
n.Int64 = i
|
||||||
|
if i == 0 {
|
||||||
|
n.IsUint = true // in case of -0.
|
||||||
|
n.Uint64 = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If an integer extraction succeeded, promote the float.
|
||||||
|
if n.IsInt {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = float64(n.Int64)
|
||||||
|
} else if n.IsUint {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = float64(n.Uint64)
|
||||||
|
} else {
|
||||||
|
f, err := strconv.ParseFloat(text, 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = f
|
||||||
|
// If a floating-point extraction succeeded, extract the int if needed.
|
||||||
|
if !n.IsInt && float64(int64(f)) == f {
|
||||||
|
n.IsInt = true
|
||||||
|
n.Int64 = int64(f)
|
||||||
|
}
|
||||||
|
if !n.IsUint && float64(uint64(f)) == f {
|
||||||
|
n.IsUint = true
|
||||||
|
n.Uint64 = uint64(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||||
|
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||||
|
// These all require that the imaginary part be zero.
|
||||||
|
func (n *NumberNode) simplifyComplex() {
|
||||||
|
n.IsFloat = imag(n.Complex128) == 0
|
||||||
|
if n.IsFloat {
|
||||||
|
n.Float64 = real(n.Complex128)
|
||||||
|
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||||
|
if n.IsInt {
|
||||||
|
n.Int64 = int64(n.Float64)
|
||||||
|
}
|
||||||
|
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||||
|
if n.IsUint {
|
||||||
|
n.Uint64 = uint64(n.Float64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) String() string {
|
||||||
|
return n.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) tree() *Tree {
|
||||||
|
return n.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) Copy() Node {
|
||||||
|
nn := new(NumberNode)
|
||||||
|
*nn = *n // Easy, fast, correct.
|
||||||
|
return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringNode holds a string constant. The value has been "unquoted".
|
||||||
|
type StringNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Quoted string // The original text of the string, with quotes.
|
||||||
|
Text string // The string, after quote processing.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||||
|
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) String() string {
|
||||||
|
return s.Quoted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) tree() *Tree {
|
||||||
|
return s.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) Copy() Node {
|
||||||
|
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endNode represents an {{end}} action.
|
||||||
|
// It does not appear in the final parse tree.
|
||||||
|
type endNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||||
|
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) String() string {
|
||||||
|
return "{{end}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) tree() *Tree {
|
||||||
|
return e.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) Copy() Node {
|
||||||
|
return e.tr.newEnd(e.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||||
|
type elseNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||||
|
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) Type() NodeType {
|
||||||
|
return nodeElse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) String() string {
|
||||||
|
return "{{else}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) tree() *Tree {
|
||||||
|
return e.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) Copy() Node {
|
||||||
|
return e.tr.newElse(e.Pos, e.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BranchNode is the common representation of if, range, and with.
|
||||||
|
type BranchNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Pipe *PipeNode // The pipeline to be evaluated.
|
||||||
|
List *ListNode // What to execute if the value is non-empty.
|
||||||
|
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) String() string {
|
||||||
|
name := ""
|
||||||
|
switch b.NodeType {
|
||||||
|
case NodeIf:
|
||||||
|
name = "if"
|
||||||
|
case NodeRange:
|
||||||
|
name = "range"
|
||||||
|
case NodeWith:
|
||||||
|
name = "with"
|
||||||
|
default:
|
||||||
|
panic("unknown branch type")
|
||||||
|
}
|
||||||
|
if b.ElseList != nil {
|
||||||
|
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) tree() *Tree {
|
||||||
|
return b.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) Copy() Node {
|
||||||
|
switch b.NodeType {
|
||||||
|
case NodeIf:
|
||||||
|
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
case NodeRange:
|
||||||
|
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
case NodeWith:
|
||||||
|
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
default:
|
||||||
|
panic("unknown branch type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfNode represents an {{if}} action and its commands.
|
||||||
|
type IfNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||||
|
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IfNode) Copy() Node {
|
||||||
|
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeNode represents a {{range}} action and its commands.
|
||||||
|
type RangeNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||||
|
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RangeNode) Copy() Node {
|
||||||
|
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNode represents a {{with}} action and its commands.
|
||||||
|
type WithNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||||
|
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WithNode) Copy() Node {
|
||||||
|
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateNode represents a {{template}} action.
|
||||||
|
type TemplateNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Name string // The name of the template (unquoted).
|
||||||
|
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||||
|
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) String() string {
|
||||||
|
if t.Pipe == nil {
|
||||||
|
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) tree() *Tree {
|
||||||
|
return t.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) Copy() Node {
|
||||||
|
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||||
|
}
|
||||||
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
Normal file
@ -0,0 +1,700 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package parse builds parse trees for templates as defined by text/template
|
||||||
|
// and html/template. Clients should use those packages to construct templates
|
||||||
|
// rather than this one, which provides shared internal data structures not
|
||||||
|
// intended for general use.
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tree is the representation of a single parsed template.
|
||||||
|
type Tree struct {
|
||||||
|
Name string // name of the template represented by the tree.
|
||||||
|
ParseName string // name of the top-level template during parsing, for error messages.
|
||||||
|
Root *ListNode // top-level root of the tree.
|
||||||
|
text string // text parsed to create the template (or its parent)
|
||||||
|
// Parsing only; cleared after parse.
|
||||||
|
funcs []map[string]interface{}
|
||||||
|
lex *lexer
|
||||||
|
token [3]item // three-token lookahead for parser.
|
||||||
|
peekCount int
|
||||||
|
vars []string // variables defined at the moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||||
|
func (t *Tree) Copy() *Tree {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Tree{
|
||||||
|
Name: t.Name,
|
||||||
|
ParseName: t.ParseName,
|
||||||
|
Root: t.Root.CopyList(),
|
||||||
|
text: t.text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||||
|
// templates described in the argument string. The top-level template will be
|
||||||
|
// given the specified name. If an error is encountered, parsing stops and an
|
||||||
|
// empty map is returned with the error.
|
||||||
|
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
||||||
|
treeSet = make(map[string]*Tree)
|
||||||
|
t := New(name)
|
||||||
|
t.text = text
|
||||||
|
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next token.
|
||||||
|
func (t *Tree) next() item {
|
||||||
|
if t.peekCount > 0 {
|
||||||
|
t.peekCount--
|
||||||
|
} else {
|
||||||
|
t.token[0] = t.lex.nextItem()
|
||||||
|
}
|
||||||
|
return t.token[t.peekCount]
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup backs the input stream up one token.
|
||||||
|
func (t *Tree) backup() {
|
||||||
|
t.peekCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup2 backs the input stream up two tokens.
|
||||||
|
// The zeroth token is already there.
|
||||||
|
func (t *Tree) backup2(t1 item) {
|
||||||
|
t.token[1] = t1
|
||||||
|
t.peekCount = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup3 backs the input stream up three tokens
|
||||||
|
// The zeroth token is already there.
|
||||||
|
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||||
|
t.token[1] = t1
|
||||||
|
t.token[2] = t2
|
||||||
|
t.peekCount = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next token.
|
||||||
|
func (t *Tree) peek() item {
|
||||||
|
if t.peekCount > 0 {
|
||||||
|
return t.token[t.peekCount-1]
|
||||||
|
}
|
||||||
|
t.peekCount = 1
|
||||||
|
t.token[0] = t.lex.nextItem()
|
||||||
|
return t.token[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextNonSpace returns the next non-space token.
|
||||||
|
func (t *Tree) nextNonSpace() (token item) {
|
||||||
|
for {
|
||||||
|
token = t.next()
|
||||||
|
if token.typ != itemSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekNonSpace returns but does not consume the next non-space token.
|
||||||
|
func (t *Tree) peekNonSpace() (token item) {
|
||||||
|
for {
|
||||||
|
token = t.next()
|
||||||
|
if token.typ != itemSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing.
|
||||||
|
|
||||||
|
// New allocates a new parse tree with the given name.
|
||||||
|
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||||
|
return &Tree{
|
||||||
|
Name: name,
|
||||||
|
funcs: funcs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||||
|
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||||
|
// which can occur in old code.
|
||||||
|
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||||
|
pos := int(n.Position())
|
||||||
|
tree := n.tree()
|
||||||
|
if tree == nil {
|
||||||
|
tree = t
|
||||||
|
}
|
||||||
|
text := tree.text[:pos]
|
||||||
|
byteNum := strings.LastIndex(text, "\n")
|
||||||
|
if byteNum == -1 {
|
||||||
|
byteNum = pos // On first line.
|
||||||
|
} else {
|
||||||
|
byteNum++ // After the newline.
|
||||||
|
byteNum = pos - byteNum
|
||||||
|
}
|
||||||
|
lineNum := 1 + strings.Count(text, "\n")
|
||||||
|
context = n.String()
|
||||||
|
if len(context) > 20 {
|
||||||
|
context = fmt.Sprintf("%.20s...", context)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf formats the error and terminates processing.
|
||||||
|
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||||
|
t.Root = nil
|
||||||
|
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// error terminates processing.
|
||||||
|
func (t *Tree) error(err error) {
|
||||||
|
t.errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect consumes the next token and guarantees it has the required type.
|
||||||
|
func (t *Tree) expect(expected itemType, context string) item {
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
if token.typ != expected {
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||||
|
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
if token.typ != expected1 && token.typ != expected2 {
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// unexpected complains about the token and terminates processing.
|
||||||
|
func (t *Tree) unexpected(token item, context string) {
|
||||||
|
t.errorf("unexpected %s in %s", token, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||||
|
func (t *Tree) recover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
if _, ok := e.(runtime.Error); ok {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
t.stopParse()
|
||||||
|
}
|
||||||
|
*errp = e.(error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// startParse initializes the parser, using the lexer.
|
||||||
|
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
||||||
|
t.Root = nil
|
||||||
|
t.lex = lex
|
||||||
|
t.vars = []string{"$"}
|
||||||
|
t.funcs = funcs
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopParse terminates parsing.
|
||||||
|
func (t *Tree) stopParse() {
|
||||||
|
t.lex = nil
|
||||||
|
t.vars = nil
|
||||||
|
t.funcs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the template definition string to construct a representation of
|
||||||
|
// the template for execution. If either action delimiter string is empty, the
|
||||||
|
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||||
|
// the treeSet map.
|
||||||
|
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||||
|
defer t.recover(&err)
|
||||||
|
t.ParseName = t.Name
|
||||||
|
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
||||||
|
t.text = text
|
||||||
|
t.parse(treeSet)
|
||||||
|
t.add(treeSet)
|
||||||
|
t.stopParse()
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds tree to the treeSet.
|
||||||
|
func (t *Tree) add(treeSet map[string]*Tree) {
|
||||||
|
tree := treeSet[t.Name]
|
||||||
|
if tree == nil || IsEmptyTree(tree.Root) {
|
||||||
|
treeSet[t.Name] = t
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !IsEmptyTree(t.Root) {
|
||||||
|
t.errorf("template: multiple definition of template %q", t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||||
|
func IsEmptyTree(n Node) bool {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case nil:
|
||||||
|
return true
|
||||||
|
case *ActionNode:
|
||||||
|
case *IfNode:
|
||||||
|
case *ListNode:
|
||||||
|
for _, node := range n.Nodes {
|
||||||
|
if !IsEmptyTree(node) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case *RangeNode:
|
||||||
|
case *TemplateNode:
|
||||||
|
case *TextNode:
|
||||||
|
return len(bytes.TrimSpace(n.Text)) == 0
|
||||||
|
case *WithNode:
|
||||||
|
default:
|
||||||
|
panic("unknown node: " + n.String())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse is the top-level parser for a template, essentially the same
|
||||||
|
// as itemList except it also parses {{define}} actions.
|
||||||
|
// It runs to EOF.
|
||||||
|
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||||
|
t.Root = t.newList(t.peek().pos)
|
||||||
|
for t.peek().typ != itemEOF {
|
||||||
|
if t.peek().typ == itemLeftDelim {
|
||||||
|
delim := t.next()
|
||||||
|
if t.nextNonSpace().typ == itemDefine {
|
||||||
|
newT := New("definition") // name will be updated once we know it.
|
||||||
|
newT.text = t.text
|
||||||
|
newT.ParseName = t.ParseName
|
||||||
|
newT.startParse(t.funcs, t.lex)
|
||||||
|
newT.parseDefinition(treeSet)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.backup2(delim)
|
||||||
|
}
|
||||||
|
n := t.textOrAction()
|
||||||
|
if n.Type() == nodeEnd {
|
||||||
|
t.errorf("unexpected %s", n)
|
||||||
|
}
|
||||||
|
t.Root.append(n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||||
|
// installs the definition in the treeSet map. The "define" keyword has already
|
||||||
|
// been scanned.
|
||||||
|
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||||
|
const context = "define clause"
|
||||||
|
name := t.expectOneOf(itemString, itemRawString, context)
|
||||||
|
var err error
|
||||||
|
t.Name, err = strconv.Unquote(name.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
t.expect(itemRightDelim, context)
|
||||||
|
var end Node
|
||||||
|
t.Root, end = t.itemList()
|
||||||
|
if end.Type() != nodeEnd {
|
||||||
|
t.errorf("unexpected %s in %s", end, context)
|
||||||
|
}
|
||||||
|
t.add(treeSet)
|
||||||
|
t.stopParse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemList:
|
||||||
|
// textOrAction*
|
||||||
|
// Terminates at {{end}} or {{else}}, returned separately.
|
||||||
|
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||||
|
list = t.newList(t.peekNonSpace().pos)
|
||||||
|
for t.peekNonSpace().typ != itemEOF {
|
||||||
|
n := t.textOrAction()
|
||||||
|
switch n.Type() {
|
||||||
|
case nodeEnd, nodeElse:
|
||||||
|
return list, n
|
||||||
|
}
|
||||||
|
list.append(n)
|
||||||
|
}
|
||||||
|
t.errorf("unexpected EOF")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// textOrAction:
|
||||||
|
// text | action
|
||||||
|
func (t *Tree) textOrAction() Node {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemElideNewline:
|
||||||
|
return t.elideNewline()
|
||||||
|
case itemText:
|
||||||
|
return t.newText(token.pos, token.val)
|
||||||
|
case itemLeftDelim:
|
||||||
|
return t.action()
|
||||||
|
default:
|
||||||
|
t.unexpected(token, "input")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// elideNewline:
|
||||||
|
// Remove newlines trailing rightDelim if \\ is present.
|
||||||
|
func (t *Tree) elideNewline() Node {
|
||||||
|
token := t.peek()
|
||||||
|
if token.typ != itemText {
|
||||||
|
t.unexpected(token, "input")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.next()
|
||||||
|
stripped := strings.TrimLeft(token.val, "\n\r")
|
||||||
|
diff := len(token.val) - len(stripped)
|
||||||
|
if diff > 0 {
|
||||||
|
// This is a bit nasty. We mutate the token in-place to remove
|
||||||
|
// preceding newlines.
|
||||||
|
token.pos += Pos(diff)
|
||||||
|
token.val = stripped
|
||||||
|
}
|
||||||
|
return t.newText(token.pos, token.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action:
|
||||||
|
// control
|
||||||
|
// command ("|" command)*
|
||||||
|
// Left delim is past. Now get actions.
|
||||||
|
// First word could be a keyword such as range.
|
||||||
|
func (t *Tree) action() (n Node) {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemElse:
|
||||||
|
return t.elseControl()
|
||||||
|
case itemEnd:
|
||||||
|
return t.endControl()
|
||||||
|
case itemIf:
|
||||||
|
return t.ifControl()
|
||||||
|
case itemRange:
|
||||||
|
return t.rangeControl()
|
||||||
|
case itemTemplate:
|
||||||
|
return t.templateControl()
|
||||||
|
case itemWith:
|
||||||
|
return t.withControl()
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
// Do not pop variables; they persist until "end".
|
||||||
|
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline:
|
||||||
|
// declarations? command ('|' command)*
|
||||||
|
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||||
|
var decl []*VariableNode
|
||||||
|
pos := t.peekNonSpace().pos
|
||||||
|
// Are there declarations?
|
||||||
|
for {
|
||||||
|
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||||
|
t.next()
|
||||||
|
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||||
|
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||||
|
// argument variable rather than a declaration. So remember the token
|
||||||
|
// adjacent to the variable so we can push it back if necessary.
|
||||||
|
tokenAfterVariable := t.peek()
|
||||||
|
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
||||||
|
t.nextNonSpace()
|
||||||
|
variable := t.newVariable(v.pos, v.val)
|
||||||
|
decl = append(decl, variable)
|
||||||
|
t.vars = append(t.vars, v.val)
|
||||||
|
if next.typ == itemChar && next.val == "," {
|
||||||
|
if context == "range" && len(decl) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.errorf("too many declarations in %s", context)
|
||||||
|
}
|
||||||
|
} else if tokenAfterVariable.typ == itemSpace {
|
||||||
|
t.backup3(v, tokenAfterVariable)
|
||||||
|
} else {
|
||||||
|
t.backup2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
||||||
|
for {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemRightDelim, itemRightParen:
|
||||||
|
if len(pipe.Cmds) == 0 {
|
||||||
|
t.errorf("missing value for %s", context)
|
||||||
|
}
|
||||||
|
if token.typ == itemRightParen {
|
||||||
|
t.backup()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||||
|
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||||
|
t.backup()
|
||||||
|
pipe.append(t.command())
|
||||||
|
default:
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||||
|
defer t.popVars(len(t.vars))
|
||||||
|
line = t.lex.lineNumber()
|
||||||
|
pipe = t.pipeline(context)
|
||||||
|
var next Node
|
||||||
|
list, next = t.itemList()
|
||||||
|
switch next.Type() {
|
||||||
|
case nodeEnd: //done
|
||||||
|
case nodeElse:
|
||||||
|
if allowElseIf {
|
||||||
|
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||||
|
// the elseControl will have left the "if" token pending. Treat
|
||||||
|
// {{if a}}_{{else if b}}_{{end}}
|
||||||
|
// as
|
||||||
|
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||||
|
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||||
|
// is assumed. This technique works even for long if-else-if chains.
|
||||||
|
// TODO: Should we allow else-if in with and range?
|
||||||
|
if t.peek().typ == itemIf {
|
||||||
|
t.next() // Consume the "if" token.
|
||||||
|
elseList = t.newList(next.Position())
|
||||||
|
elseList.append(t.ifControl())
|
||||||
|
// Do not consume the next item - only one {{end}} required.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseList, next = t.itemList()
|
||||||
|
if next.Type() != nodeEnd {
|
||||||
|
t.errorf("expected end; found %s", next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pipe.Position(), line, pipe, list, elseList
|
||||||
|
}
|
||||||
|
|
||||||
|
// If:
|
||||||
|
// {{if pipeline}} itemList {{end}}
|
||||||
|
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// If keyword is past.
|
||||||
|
func (t *Tree) ifControl() Node {
|
||||||
|
return t.newIf(t.parseControl(true, "if"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range:
|
||||||
|
// {{range pipeline}} itemList {{end}}
|
||||||
|
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// Range keyword is past.
|
||||||
|
func (t *Tree) rangeControl() Node {
|
||||||
|
return t.newRange(t.parseControl(false, "range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// With:
|
||||||
|
// {{with pipeline}} itemList {{end}}
|
||||||
|
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// If keyword is past.
|
||||||
|
func (t *Tree) withControl() Node {
|
||||||
|
return t.newWith(t.parseControl(false, "with"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// End:
|
||||||
|
// {{end}}
|
||||||
|
// End keyword is past.
|
||||||
|
func (t *Tree) endControl() Node {
|
||||||
|
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else:
|
||||||
|
// {{else}}
|
||||||
|
// Else keyword is past.
|
||||||
|
func (t *Tree) elseControl() Node {
|
||||||
|
// Special case for "else if".
|
||||||
|
peek := t.peekNonSpace()
|
||||||
|
if peek.typ == itemIf {
|
||||||
|
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||||
|
return t.newElse(peek.pos, t.lex.lineNumber())
|
||||||
|
}
|
||||||
|
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template:
|
||||||
|
// {{template stringValue pipeline}}
|
||||||
|
// Template keyword is past. The name must be something that can evaluate
|
||||||
|
// to a string.
|
||||||
|
func (t *Tree) templateControl() Node {
|
||||||
|
var name string
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
switch token.typ {
|
||||||
|
case itemString, itemRawString:
|
||||||
|
s, err := strconv.Unquote(token.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
name = s
|
||||||
|
default:
|
||||||
|
t.unexpected(token, "template invocation")
|
||||||
|
}
|
||||||
|
var pipe *PipeNode
|
||||||
|
if t.nextNonSpace().typ != itemRightDelim {
|
||||||
|
t.backup()
|
||||||
|
// Do not pop variables; they persist until "end".
|
||||||
|
pipe = t.pipeline("template")
|
||||||
|
}
|
||||||
|
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// command:
|
||||||
|
// operand (space operand)*
|
||||||
|
// space-separated arguments up to a pipeline character or right delimiter.
|
||||||
|
// we consume the pipe character but leave the right delim to terminate the action.
|
||||||
|
func (t *Tree) command() *CommandNode {
|
||||||
|
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||||
|
for {
|
||||||
|
t.peekNonSpace() // skip leading spaces.
|
||||||
|
operand := t.operand()
|
||||||
|
if operand != nil {
|
||||||
|
cmd.append(operand)
|
||||||
|
}
|
||||||
|
switch token := t.next(); token.typ {
|
||||||
|
case itemSpace:
|
||||||
|
continue
|
||||||
|
case itemError:
|
||||||
|
t.errorf("%s", token.val)
|
||||||
|
case itemRightDelim, itemRightParen:
|
||||||
|
t.backup()
|
||||||
|
case itemPipe:
|
||||||
|
default:
|
||||||
|
t.errorf("unexpected %s in operand; missing space?", token)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(cmd.Args) == 0 {
|
||||||
|
t.errorf("empty command")
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// operand:
|
||||||
|
// term .Field*
|
||||||
|
// An operand is a space-separated component of a command,
|
||||||
|
// a term possibly followed by field accesses.
|
||||||
|
// A nil return means the next item is not an operand.
|
||||||
|
func (t *Tree) operand() Node {
|
||||||
|
node := t.term()
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if t.peek().typ == itemField {
|
||||||
|
chain := t.newChain(t.peek().pos, node)
|
||||||
|
for t.peek().typ == itemField {
|
||||||
|
chain.Add(t.next().val)
|
||||||
|
}
|
||||||
|
// Compatibility with original API: If the term is of type NodeField
|
||||||
|
// or NodeVariable, just put more fields on the original.
|
||||||
|
// Otherwise, keep the Chain node.
|
||||||
|
// TODO: Switch to Chains always when we can.
|
||||||
|
switch node.Type() {
|
||||||
|
case NodeField:
|
||||||
|
node = t.newField(chain.Position(), chain.String())
|
||||||
|
case NodeVariable:
|
||||||
|
node = t.newVariable(chain.Position(), chain.String())
|
||||||
|
default:
|
||||||
|
node = chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// term:
|
||||||
|
// literal (number, string, nil, boolean)
|
||||||
|
// function (identifier)
|
||||||
|
// .
|
||||||
|
// .Field
|
||||||
|
// $
|
||||||
|
// '(' pipeline ')'
|
||||||
|
// A term is a simple "expression".
|
||||||
|
// A nil return means the next item is not a term.
|
||||||
|
func (t *Tree) term() Node {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemError:
|
||||||
|
t.errorf("%s", token.val)
|
||||||
|
case itemIdentifier:
|
||||||
|
if !t.hasFunction(token.val) {
|
||||||
|
t.errorf("function %q not defined", token.val)
|
||||||
|
}
|
||||||
|
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||||
|
case itemDot:
|
||||||
|
return t.newDot(token.pos)
|
||||||
|
case itemNil:
|
||||||
|
return t.newNil(token.pos)
|
||||||
|
case itemVariable:
|
||||||
|
return t.useVar(token.pos, token.val)
|
||||||
|
case itemField:
|
||||||
|
return t.newField(token.pos, token.val)
|
||||||
|
case itemBool:
|
||||||
|
return t.newBool(token.pos, token.val == "true")
|
||||||
|
case itemCharConstant, itemComplex, itemNumber:
|
||||||
|
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
return number
|
||||||
|
case itemLeftParen:
|
||||||
|
pipe := t.pipeline("parenthesized pipeline")
|
||||||
|
if token := t.next(); token.typ != itemRightParen {
|
||||||
|
t.errorf("unclosed right paren: unexpected %s", token)
|
||||||
|
}
|
||||||
|
return pipe
|
||||||
|
case itemString, itemRawString:
|
||||||
|
s, err := strconv.Unquote(token.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
return t.newString(token.pos, token.val, s)
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasFunction reports if a function name exists in the Tree's maps.
|
||||||
|
func (t *Tree) hasFunction(name string) bool {
|
||||||
|
for _, funcMap := range t.funcs {
|
||||||
|
if funcMap == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if funcMap[name] != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// popVars trims the variable list to the specified length
|
||||||
|
func (t *Tree) popVars(n int) {
|
||||||
|
t.vars = t.vars[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// useVar returns a node for a variable reference. It errors if the
|
||||||
|
// variable is not defined.
|
||||||
|
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||||
|
v := t.newVariable(pos, name)
|
||||||
|
for _, varName := range t.vars {
|
||||||
|
if varName == v.Ident[0] {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.errorf("undefined variable %q", v.Ident[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// common holds the information shared by related templates.
|
||||||
|
type common struct {
|
||||||
|
tmpl map[string]*Template
|
||||||
|
// We use two maps, one for parsing and one for execution.
|
||||||
|
// This separation makes the API cleaner since it doesn't
|
||||||
|
// expose reflection to the client.
|
||||||
|
parseFuncs FuncMap
|
||||||
|
execFuncs map[string]reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template is the representation of a parsed template. The *parse.Tree
|
||||||
|
// field is exported only for use by html/template and should be treated
|
||||||
|
// as unexported by all other clients.
|
||||||
|
type Template struct {
|
||||||
|
name string
|
||||||
|
*parse.Tree
|
||||||
|
*common
|
||||||
|
leftDelim string
|
||||||
|
rightDelim string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new template with the given name.
|
||||||
|
func New(name string) *Template {
|
||||||
|
return &Template{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the template.
|
||||||
|
func (t *Template) Name() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new template associated with the given one and with the same
|
||||||
|
// delimiters. The association, which is transitive, allows one template to
|
||||||
|
// invoke another with a {{template}} action.
|
||||||
|
func (t *Template) New(name string) *Template {
|
||||||
|
t.init()
|
||||||
|
return &Template{
|
||||||
|
name: name,
|
||||||
|
common: t.common,
|
||||||
|
leftDelim: t.leftDelim,
|
||||||
|
rightDelim: t.rightDelim,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) init() {
|
||||||
|
if t.common == nil {
|
||||||
|
t.common = new(common)
|
||||||
|
t.tmpl = make(map[string]*Template)
|
||||||
|
t.parseFuncs = make(FuncMap)
|
||||||
|
t.execFuncs = make(map[string]reflect.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the template, including all associated
|
||||||
|
// templates. The actual representation is not copied, but the name space of
|
||||||
|
// associated templates is, so further calls to Parse in the copy will add
|
||||||
|
// templates to the copy but not to the original. Clone can be used to prepare
|
||||||
|
// common templates and use them with variant definitions for other templates
|
||||||
|
// by adding the variants after the clone is made.
|
||||||
|
func (t *Template) Clone() (*Template, error) {
|
||||||
|
nt := t.copy(nil)
|
||||||
|
nt.init()
|
||||||
|
nt.tmpl[t.name] = nt
|
||||||
|
for k, v := range t.tmpl {
|
||||||
|
if k == t.name { // Already installed.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The associated templates share nt's common structure.
|
||||||
|
tmpl := v.copy(nt.common)
|
||||||
|
nt.tmpl[k] = tmpl
|
||||||
|
}
|
||||||
|
for k, v := range t.parseFuncs {
|
||||||
|
nt.parseFuncs[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range t.execFuncs {
|
||||||
|
nt.execFuncs[k] = v
|
||||||
|
}
|
||||||
|
return nt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a shallow copy of t, with common set to the argument.
|
||||||
|
func (t *Template) copy(c *common) *Template {
|
||||||
|
nt := New(t.name)
|
||||||
|
nt.Tree = t.Tree
|
||||||
|
nt.common = c
|
||||||
|
nt.leftDelim = t.leftDelim
|
||||||
|
nt.rightDelim = t.rightDelim
|
||||||
|
return nt
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddParseTree creates a new template with the name and parse tree
|
||||||
|
// and associates it with t.
|
||||||
|
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||||
|
if t.common != nil && t.tmpl[name] != nil {
|
||||||
|
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
||||||
|
}
|
||||||
|
nt := t.New(name)
|
||||||
|
nt.Tree = tree
|
||||||
|
t.tmpl[name] = nt
|
||||||
|
return nt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates returns a slice of the templates associated with t, including t
|
||||||
|
// itself.
|
||||||
|
func (t *Template) Templates() []*Template {
|
||||||
|
if t.common == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Return a slice so we don't expose the map.
|
||||||
|
m := make([]*Template, 0, len(t.tmpl))
|
||||||
|
for _, v := range t.tmpl {
|
||||||
|
m = append(m, v)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delims sets the action delimiters to the specified strings, to be used in
|
||||||
|
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||||
|
// definitions will inherit the settings. An empty delimiter stands for the
|
||||||
|
// corresponding default: {{ or }}.
|
||||||
|
// The return value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Delims(left, right string) *Template {
|
||||||
|
t.leftDelim = left
|
||||||
|
t.rightDelim = right
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funcs adds the elements of the argument map to the template's function map.
|
||||||
|
// It panics if a value in the map is not a function with appropriate return
|
||||||
|
// type. However, it is legal to overwrite elements of the map. The return
|
||||||
|
// value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||||
|
t.init()
|
||||||
|
addValueFuncs(t.execFuncs, funcMap)
|
||||||
|
addFuncs(t.parseFuncs, funcMap)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns the template with the given name that is associated with t,
|
||||||
|
// or nil if there is no such template.
|
||||||
|
func (t *Template) Lookup(name string) *Template {
|
||||||
|
if t.common == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return t.tmpl[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a string into a template. Nested template definitions will be
|
||||||
|
// associated with the top-level template t. Parse may be called multiple times
|
||||||
|
// to parse definitions of templates to associate with t. It is an error if a
|
||||||
|
// resulting template is non-empty (contains content other than template
|
||||||
|
// definitions) and would replace a non-empty template with the same name.
|
||||||
|
// (In multiple calls to Parse with the same receiver template, only one call
|
||||||
|
// can contain text other than space, comments, and template definitions.)
|
||||||
|
func (t *Template) Parse(text string) (*Template, error) {
|
||||||
|
t.init()
|
||||||
|
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||||
|
for name, tree := range trees {
|
||||||
|
// If the name we parsed is the name of this template, overwrite this template.
|
||||||
|
// The associate method checks it's not a redefinition.
|
||||||
|
tmpl := t
|
||||||
|
if name != t.name {
|
||||||
|
tmpl = t.New(name)
|
||||||
|
}
|
||||||
|
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
||||||
|
if replace, err := t.associate(tmpl, tree); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if replace {
|
||||||
|
tmpl.Tree = tree
|
||||||
|
}
|
||||||
|
tmpl.leftDelim = t.leftDelim
|
||||||
|
tmpl.rightDelim = t.rightDelim
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// associate installs the new template into the group of templates associated
|
||||||
|
// with t. It is an error to reuse a name except to overwrite an empty
|
||||||
|
// template. The two are already known to share the common structure.
|
||||||
|
// The boolean return value reports wither to store this tree as t.Tree.
|
||||||
|
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
||||||
|
if new.common != t.common {
|
||||||
|
panic("internal error: associate not common")
|
||||||
|
}
|
||||||
|
name := new.name
|
||||||
|
if old := t.tmpl[name]; old != nil {
|
||||||
|
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
||||||
|
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
||||||
|
if newIsEmpty {
|
||||||
|
// Whether old is empty or not, new is empty; no reason to replace old.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if !oldIsEmpty {
|
||||||
|
return false, fmt.Errorf("template: redefinition of template %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.tmpl[name] = new
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
19
vendor/github.com/alecthomas/units/COPYING
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (C) 2014 Alec Thomas
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
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.
|
||||||
11
vendor/github.com/alecthomas/units/README.md
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Units - Helpful unit multipliers and functions for Go
|
||||||
|
|
||||||
|
The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package.
|
||||||
|
|
||||||
|
It allows for code like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
n, err := ParseBase2Bytes("1KB")
|
||||||
|
// n == 1024
|
||||||
|
n = units.Mebibyte * 512
|
||||||
|
```
|
||||||
83
vendor/github.com/alecthomas/units/bytes.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte,
|
||||||
|
// etc.).
|
||||||
|
type Base2Bytes int64
|
||||||
|
|
||||||
|
// Base-2 byte units.
|
||||||
|
const (
|
||||||
|
Kibibyte Base2Bytes = 1024
|
||||||
|
KiB = Kibibyte
|
||||||
|
Mebibyte = Kibibyte * 1024
|
||||||
|
MiB = Mebibyte
|
||||||
|
Gibibyte = Mebibyte * 1024
|
||||||
|
GiB = Gibibyte
|
||||||
|
Tebibyte = Gibibyte * 1024
|
||||||
|
TiB = Tebibyte
|
||||||
|
Pebibyte = Tebibyte * 1024
|
||||||
|
PiB = Pebibyte
|
||||||
|
Exbibyte = Pebibyte * 1024
|
||||||
|
EiB = Exbibyte
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bytesUnitMap = MakeUnitMap("iB", "B", 1024)
|
||||||
|
oldBytesUnitMap = MakeUnitMap("B", "B", 1024)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB
|
||||||
|
// and KiB are both 1024.
|
||||||
|
func ParseBase2Bytes(s string) (Base2Bytes, error) {
|
||||||
|
n, err := ParseUnit(s, bytesUnitMap)
|
||||||
|
if err != nil {
|
||||||
|
n, err = ParseUnit(s, oldBytesUnitMap)
|
||||||
|
}
|
||||||
|
return Base2Bytes(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Base2Bytes) String() string {
|
||||||
|
return ToString(int64(b), 1024, "iB", "B")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricBytesUnitMap = MakeUnitMap("B", "B", 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetricBytes are SI byte units (1000 bytes in a kilobyte).
|
||||||
|
type MetricBytes SI
|
||||||
|
|
||||||
|
// SI base-10 byte units.
|
||||||
|
const (
|
||||||
|
Kilobyte MetricBytes = 1000
|
||||||
|
KB = Kilobyte
|
||||||
|
Megabyte = Kilobyte * 1000
|
||||||
|
MB = Megabyte
|
||||||
|
Gigabyte = Megabyte * 1000
|
||||||
|
GB = Gigabyte
|
||||||
|
Terabyte = Gigabyte * 1000
|
||||||
|
TB = Terabyte
|
||||||
|
Petabyte = Terabyte * 1000
|
||||||
|
PB = Petabyte
|
||||||
|
Exabyte = Petabyte * 1000
|
||||||
|
EB = Exabyte
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes.
|
||||||
|
func ParseMetricBytes(s string) (MetricBytes, error) {
|
||||||
|
n, err := ParseUnit(s, metricBytesUnitMap)
|
||||||
|
return MetricBytes(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MetricBytes) String() string {
|
||||||
|
return ToString(int64(m), 1000, "B", "B")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseStrictBytes supports both iB and B suffixes for base 2 and metric,
|
||||||
|
// respectively. That is, KiB represents 1024 and KB represents 1000.
|
||||||
|
func ParseStrictBytes(s string) (int64, error) {
|
||||||
|
n, err := ParseUnit(s, bytesUnitMap)
|
||||||
|
if err != nil {
|
||||||
|
n, err = ParseUnit(s, metricBytesUnitMap)
|
||||||
|
}
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||