327 Commits
v3.9 ... v4.9

Author SHA1 Message Date
arraykeys@gmail.com
4214ec4239 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-06-06 15:06:56 +08:00
arraykeys@gmail.com
504de47999 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-06-06 14:44:04 +08:00
arraykeys@gmail.com
e185d734d0 mux内网穿透切换smux到yamux
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-05-31 16:26:40 +08:00
arraykeys@gmail.com
9b1ef52649 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-05-31 11:39:50 +08:00
arraykeys@gmail.com
5c9fc850d8 fix #84
fix #81

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-05-31 11:39:29 +08:00
snail007
ff96e52a33 Merge pull request #89 from moonfruit/dev
HTTP Basic 认证失败返回的 WWW-Authenticate 更正为 Proxy-Authenticate
2018-05-24 11:22:35 +08:00
MoonFruit
78004bcd39 HTTP Basic 认证失败返回的 WWW-Authenticate 更正为 Proxy-Authenticate 2018-05-24 09:58:04 +08:00
arraykeys@gmail.com
b16decf976 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-23 14:38:19 +08:00
arraykeys@gmail.com
828636553d Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-23 14:23:43 +08:00
arraykeys@gmail.com
bfcc27e70f fix #85
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-05-22 12:34:18 +08:00
arraykeys@gmail.com
7cb7d34d42 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-22 12:01:55 +08:00
arraykeys@gmail.com
5276154401 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-22 11:32:31 +08:00
arraykeys@gmail.com
dad091441e Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-22 11:30:19 +08:00
arraykeys@gmail.com
81ff3dadd5 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-22 11:26:34 +08:00
arraykeys@gmail.com
f559fb1cae fix #58
fix #80

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-05-22 11:21:58 +08:00
arraykeys@gmail.com
8649bbc191 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-21 16:04:40 +08:00
snail007
d775339948 Merge pull request #83 from yelongyu/dev
Update README_ZH.md
2018-05-20 22:46:51 +08:00
yelongyu
69a5b906e0 Update README_ZH.md
删除多余的"域名"
2018-05-20 17:33:49 +08:00
yelongyu
2d66cc6215 Update README_ZH.md
添加多级加密HTTP代理设置注意事项
2018-05-20 17:26:46 +08:00
arraykeys@gmail.com
8d74baf48c Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-16 15:59:53 +08:00
arraykeys@gmail.com
d7641c4483 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-16 11:23:54 +08:00
arraykeys@gmail.com
ffe54c3af7 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-05-15 10:58:17 +08:00
arraykeys@gmail.com
53df3b5578 add docker support
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-05-15 10:56:57 +08:00
snail007
54c22f1410 Merge pull request #77 from FarhadF/dev
Added Dockerfile
2018-05-15 08:58:30 +08:00
FarhadF
366b7e04f3 Added Dockerfile 2018-05-14 17:33:00 +04:30
snail007
a33a4d2bd3 Merge pull request #74 from whgfu/dev
Dev-更新说明文件中socks5 http api认证中的 url参数为三个
2018-05-14 20:53:01 +08:00
粥冰涅槃
eb00d570a8 更新说明文件中socks5 http api认证中的 url参数为三个 2018-05-14 18:42:01 +08:00
whgfu
500142f4c8 Merge pull request #1 from snail007/dev
Dev
2018-05-14 18:31:11 +08:00
arraykeys
bffd5891cc no message 2018-05-08 08:45:29 +08:00
arraykeys
cf2e6f9ff0 no message 2018-05-08 07:28:05 +08:00
arraykeys
ed4b8d11e3 no message 2018-05-07 20:58:59 +08:00
arraykeys
905c1eac63 no message 2018-05-07 20:48:32 +08:00
arraykeys@gmail.com
61872133b1 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-07 17:46:23 +08:00
arraykeys@gmail.com
e18f53a5bb Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-07 17:32:38 +08:00
arraykeys@gmail.com
947fb51963 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-07 17:25:47 +08:00
arraykeys@gmail.com
7aeef3f8ba Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-07 17:14:20 +08:00
arraykeys@gmail.com
b42f6a6364 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-07 16:23:47 +08:00
arraykeys@gmail.com
92f4d31dfc Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-05-07 16:23:01 +08:00
arraykeys@gmail.com
4f11593f26 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-05-07 16:21:58 +08:00
arraykeys
795d63879f no message 2018-05-04 18:24:52 +08:00
arraykeys
1c46eeaf43 no message 2018-05-04 18:10:09 +08:00
arraykeys
6f47d12498 no message 2018-05-04 17:49:56 +08:00
arraykeys
e6c56675ca no message 2018-05-04 17:42:59 +08:00
snail007
ff92c96d8d Merge pull request #70 from yincongcyincong/dev
Update README.md
2018-05-03 23:15:19 +08:00
yincongcyincong
fed2afb964 Update README.md 2018-05-03 18:42:42 +08:00
arraykeys@gmail.com
b3feff7843 1.修复了多个服务同时开启日志,只会输出到最后一个日志文件的bug.
2.增加了获取sdk版本的Version()方法.

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-05-03 17:59:06 +08:00
arraykeys@gmail.com
edb2fb3458 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-28 14:18:03 +08:00
arraykeys@gmail.com
dc51a0bd9d Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-28 14:15:47 +08:00
arraykeys@gmail.com
34b30ac8c9 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-04-28 14:13:50 +08:00
arraykeys@gmail.com
8122af9096 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-28 14:13:20 +08:00
snail007
2b267fe4bb Merge pull request #66 from shoaly/patch-4
good job
2018-04-28 13:54:32 +08:00
shoaly
90bf483976 Update README_ZH.md 2018-04-28 13:45:41 +08:00
arraykeys@gmail.com
b109f273a5 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-27 16:28:14 +08:00
arraykeys@gmail.com
482977a4ac Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-27 15:27:13 +08:00
arraykeys@gmail.com
515dcdbf1f Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-27 14:59:02 +08:00
arraykeys@gmail.com
0e033c1d85 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-27 12:22:37 +08:00
arraykeys@gmail.com
ad2441de3b Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-27 12:08:31 +08:00
arraykeys@gmail.com
7a881b3625 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-04-27 11:58:50 +08:00
arraykeys@gmail.com
faf61fdd60 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-27 11:58:27 +08:00
snail007
2b3d23f77c Merge pull request #63 from yincongcyincong/dev
Update README.md
2018-04-26 21:42:48 +08:00
yincongcyincong
06e1247706 Update README.md 2018-04-26 18:56:57 +08:00
arraykeys@gmail.com
6b5fdef8c7 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-26 17:43:53 +08:00
arraykeys@gmail.com
48b1621ee4 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-04-26 17:37:56 +08:00
snail007
c2ce973f61 Merge pull request #61 from onetwotrip/dev
Fix imports path
2018-04-26 17:37:35 +08:00
arraykeys@gmail.com
cac648cc32 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-26 17:37:03 +08:00
Maxim Pogozhiy
b9bffcdf4b Fix imports path 2018-04-25 11:28:20 +03:00
snail007
7a1e5c5de7 Merge pull request #62 from yincongcyincong/dev
en doc
2018-04-25 15:54:41 +08:00
yc
e24dfc856a Merge branch 'snail007-dev' into dev 2018-04-24 19:53:43 +08:00
yc
66a115c764 update readme 2018-04-24 19:53:27 +08:00
yincongcyincong
a6e80a30dc Update README.md 2018-04-24 19:36:30 +08:00
arraykeys@gmail.com
52a2771382 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-24 18:25:04 +08:00
arraykeys@gmail.com
6b2b75bc50 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-24 18:18:49 +08:00
arraykeys@gmail.com
893baff6c6 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev
# Conflicts:
#	sdk/windows-linux/release_linux.sh

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-24 18:17:25 +08:00
arraykeys@gmail.com
de62d956dd Merge branch 'master' of https://github.com/snail007/goproxy.git into dev 2018-04-24 18:13:28 +08:00
arraykeys@gmail.com
b59cf1f144 增加了大量插图,优化了多链接版本内网穿透,实现了心跳机制.
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-24 18:09:33 +08:00
arraykeys
8b5cc3fb89 no message 2018-04-21 11:21:04 +08:00
arraykeys@gmail.com
fbd8c67649 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-20 13:35:05 +08:00
arraykeys
c6f6266592 no message 2018-04-20 13:31:58 +08:00
arraykeys
ab72640ffd Merge branch 'dev' of github.com:snail007/goproxy into dev
* 'dev' of github.com:snail007/goproxy:
  fix #56
  Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-20 13:23:31 +08:00
arraykeys
905bfff92b no message 2018-04-20 13:23:08 +08:00
arraykeys@gmail.com
4ef33d0ffd fix #56
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-19 14:55:56 +08:00
arraykeys@gmail.com
1597363dd1 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-18 15:27:32 +08:00
arraykeys
27896a0563 no message 2018-04-18 13:22:44 +08:00
arraykeys
bef385cfd1 no message 2018-04-18 13:11:43 +08:00
arraykeys@gmail.com
98e0154baa Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-18 11:19:51 +08:00
arraykeys@gmail.com
01961a798a Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-04-18 10:57:06 +08:00
arraykeys
d5a460bd09 no message 2018-04-18 10:55:54 +08:00
arraykeys@gmail.com
e94605644f Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-04-18 10:53:39 +08:00
arraykeys@gmail.com
8dc206e2d6 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-18 10:53:14 +08:00
arraykeys
e8acbbfabf mac sdk 2018-04-18 10:52:57 +08:00
arraykeys
17335eb92b no message 2018-04-17 22:25:10 +08:00
arraykeys
d2051e6e37 no message 2018-04-17 18:24:01 +08:00
arraykeys
0aa0e7c550 add windows sdk demo 2018-04-17 17:44:16 +08:00
arraykeys
57935c2296 no message 2018-04-17 13:57:52 +08:00
arraykeys@gmail.com
9d4930b29d Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-17 13:45:31 +08:00
arraykeys@gmail.com
66206e63b3 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-17 13:36:19 +08:00
arraykeys@gmail.com
19baccb91a add linux .so and windows .dll sdk support,
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-16 18:33:10 +08:00
yincongcyincong
98bbd68448 Update README.md 2018-04-14 11:37:09 +08:00
yincongcyincong
45dee58203 Update README.md 2018-04-14 11:30:29 +08:00
yincongcyincong
e2557c44c4 Update README.md 2018-04-14 10:29:21 +08:00
arraykeys@gmail.com
f0ed6d73e4 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-13 18:31:12 +08:00
arraykeys@gmail.com
47790d1d58 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-13 18:23:26 +08:00
arraykeys@gmail.com
9aff5eda38 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-13 18:21:26 +08:00
arraykeys@gmail.com
eb2f055e07 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-13 18:14:32 +08:00
arraykeys@gmail.com
0998c06195 Add compress and encryt support on (tcp|tls|kcp) transport layer
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-13 17:23:42 +08:00
arraykeys@gmail.com
cf22866b2a Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-13 14:12:05 +08:00
arraykeys@gmail.com
16bb452640 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-12 17:03:16 +08:00
arraykeys@gmail.com
6f1d826ef5 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-12 16:45:39 +08:00
arraykeys@gmail.com
5a68fb3c3d Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-12 16:42:28 +08:00
arraykeys@gmail.com
350eb5b6ed Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-12 16:41:03 +08:00
arraykeys@gmail.com
d48f3b3323 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-12 16:36:36 +08:00
arraykeys@gmail.com
7a1491c7b3 android sdk
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-12 16:32:56 +08:00
arraykeys@gmail.com
68deae6bf8 优化服务stop方法,方便sdk开发.
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-10 18:32:17 +08:00
arraykeys@gmail.com
2086966a89 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-09 16:21:27 +08:00
arraykeys@gmail.com
3aba428b76 增加gomobile sdk对android/ios进行支持.
优化错误控制

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-04-09 16:16:55 +08:00
arraykeys@gmail.com
a11ce38747 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-04-03 17:34:33 +08:00
arraykeys@gmail.com
f7b363ec73 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-29 17:31:38 +08:00
arraykeys@gmail.com
4c33a1e9b2 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-29 17:25:32 +08:00
arraykeys@gmail.com
c4d9382ac7 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-29 16:59:52 +08:00
arraykeys@gmail.com
7df2d990e7 Merge branch 'dev' 2018-03-28 14:10:10 +08:00
snail007
900b75ddcd Merge pull request #48 from yincongcyincong/dev
Dev
2018-03-28 14:08:28 +08:00
yincongcyincong
a5d199fb1c Update README.md 2018-03-28 14:06:19 +08:00
yincongcyincong
2d8190873f Update README.md 2018-03-28 13:56:02 +08:00
yincongcyincong
3eff793ac2 Update README.md 2018-03-28 13:53:09 +08:00
arraykeys@gmail.com
27ce6e1bd2 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:34:08 +08:00
arraykeys@gmail.com
b11c7e632b fix #47
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-28 13:30:09 +08:00
arraykeys@gmail.com
d911be7d80 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:27:59 +08:00
arraykeys@gmail.com
bd056d74cc Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:25:47 +08:00
arraykeys@gmail.com
69b65a37ca Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:25:17 +08:00
arraykeys@gmail.com
dd52ad8a8a Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:22:37 +08:00
arraykeys@gmail.com
59b5ef2df4 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:21:02 +08:00
arraykeys@gmail.com
4a34566f08 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-28 13:14:06 +08:00
arraykeys@gmail.com
d81d5ffe06 socks5 client server done
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-26 18:42:44 +08:00
arraykeys@gmail.com
59c9148875 optimize timeout of http(s)\socks\nat
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-21 18:17:20 +08:00
arraykeys@gmail.com
40bce3e736 optimize http basic auth
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-21 16:57:46 +08:00
arraykeys@gmail.com
d4c0775b4a Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-16 16:16:11 +08:00
arraykeys@gmail.com
7f983152b7 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-14 09:37:57 +08:00
arraykeys@gmail.com
bab4325414 server add safe close user conn
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-13 18:36:34 +08:00
arraykeys@gmail.com
0d85c7dd7d bridge add timeout
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-13 17:31:51 +08:00
arraykeys@gmail.com
f756d62b19 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-13 17:14:25 +08:00
arraykeys@gmail.com
2d1c1449aa Merge branch 'master' of https://github.com/snail007/goproxy.git 2018-03-13 10:29:50 +08:00
arraykeys@gmail.com
f348298acd v4.5 2018-03-13 10:29:26 +08:00
arraykeys@gmail.com
ad47104fc7 v4.5 2018-03-13 10:04:18 +08:00
snail007
4d4fb64b59 Update mux_bridge.go 2018-03-12 19:27:37 +08:00
arraykeys@gmail.com
9ce3a6e468 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-12 18:15:14 +08:00
arraykeys@gmail.com
34e9e362b9 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-12 17:31:35 +08:00
arraykeys@gmail.com
f87cbf73e8 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-03-12 13:59:47 +08:00
arraykeys@gmail.com
362ada2ebb v4.5
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-12 13:59:25 +08:00
snail007
b4b7221dab Merge pull request #44 from snail007/dev
Update README_ZH.md
2018-03-10 17:00:50 +08:00
snail007
2d8dc56f4e Update README_ZH.md 2018-03-10 17:00:03 +08:00
arraykeys@gmail.com
1cf4313d12 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-09 17:16:21 +08:00
arraykeys@gmail.com
7bb8f19b90 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-09 16:02:05 +08:00
arraykeys@gmail.com
1263a4e751 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-03-09 09:50:38 +08:00
arraykeys@gmail.com
1a432a9b79 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-09 09:50:21 +08:00
snail007
8951fdbd59 Merge pull request #42 from yincongcyincong/dev
Update README.md
2018-03-09 08:36:10 +08:00
yincongcyincong
77129367fe Update README.md 2018-03-08 20:50:36 +08:00
arraykeys@gmail.com
ae2e1e0933 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-03-08 18:43:07 +08:00
arraykeys@gmail.com
4143f14fbd optimise nat forwarding in different lan
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-08 18:42:50 +08:00
snail007
8e3e262c2f Merge pull request #41 from yincongcyincong/dev
Dev
2018-03-08 13:46:47 +08:00
arraykeys@gmail.com
6f11deab96 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 13:45:54 +08:00
arraykeys@gmail.com
a17acd7351 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 12:08:25 +08:00
arraykeys@gmail.com
1cbb4195e4 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 12:08:02 +08:00
arraykeys@gmail.com
c471dd8297 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 12:07:44 +08:00
arraykeys@gmail.com
25deffb7d6 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 12:07:19 +08:00
arraykeys@gmail.com
7eb0e0040e Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 12:06:42 +08:00
arraykeys@gmail.com
15994988be Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-08 12:04:26 +08:00
arraykeys@gmail.com
ae293a6102 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-03-08 11:52:06 +08:00
arraykeys@gmail.com
5ed4702b62 add kcp config args
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-08 11:51:34 +08:00
yc
287ddc3424 Merge branch 'snail007-dev' into dev 2018-03-08 09:56:52 +08:00
yc
57a4227007 fix conflick 2018-03-08 09:56:21 +08:00
yc
c9eacd1bf2 Merge branch 'dev' of https://github.com/yincongcyincong/goproxy into dev 2018-03-08 09:50:59 +08:00
yincongcyincong
5f38162fbb Update README.md 2018-03-07 23:30:05 +08:00
snail007
c755f75a11 Update README_ZH.md 2018-03-07 23:09:08 +08:00
yincongcyincong
ac9eb64501 Update README.md 2018-03-07 13:34:16 +08:00
yincongcyincong
3dd013c13c Update README.md 2018-03-07 08:01:32 +08:00
yincongcyincong
ab0205587a Update README.md 2018-03-06 18:28:54 +08:00
arraykeys@gmail.com
70955878c9 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 18:11:13 +08:00
arraykeys@gmail.com
86f017d92f Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:38:37 +08:00
arraykeys@gmail.com
446cc3f9a7 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:37:23 +08:00
arraykeys@gmail.com
6529921d71 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:24:28 +08:00
arraykeys@gmail.com
52e441c111 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:21:44 +08:00
arraykeys@gmail.com
a6b169d336 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:20:33 +08:00
arraykeys@gmail.com
5514cfee6c Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:19:34 +08:00
arraykeys@gmail.com
b1de184bda Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:17:09 +08:00
arraykeys@gmail.com
af2405ba48 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-06 17:15:34 +08:00
arraykeys@gmail.com
bee80330b0 sps done
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-06 17:13:43 +08:00
arraykeys@gmail.com
f1de8659b7 prepare for sps
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-05 18:46:19 +08:00
arraykeys@gmail.com
bfc5835d82 Merge branch 'master' of https://github.com/snail007/goproxy.git into dev 2018-03-05 11:22:33 +08:00
arraykeys@gmail.com
82bc3e27d6 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-05 11:22:15 +08:00
arraykeys@gmail.com
5436a95430 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-05 11:15:30 +08:00
arraykeys@gmail.com
2c675f2cbe Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-03-05 09:46:07 +08:00
arraykeys@gmail.com
8e9427b0c0 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-05 09:46:00 +08:00
arraykeys@gmail.com
edcf78f77c Merge branch 'master' into dev 2018-03-05 09:45:42 +08:00
snail007
8f88d14c07 Update README.md 2018-03-01 19:40:33 +08:00
snail007
1372801b6f Update README_ZH.md 2018-03-01 19:39:08 +08:00
arraykeys@gmail.com
2af904f442 fix dns Resolve
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-03-01 15:01:08 +08:00
arraykeys@gmail.com
885b27e0d1 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-03-01 13:55:53 +08:00
arraykeys@gmail.com
0207e4731f Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-03-01 13:55:25 +08:00
arraykeys@gmail.com
038b6749a3 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-02-28 17:55:18 +08:00
snail007
ead577cbfb Merge pull request #40 from yincongcyincong/dev
Dev
2018-02-28 16:28:10 +08:00
yincongcyincong
26e9231e48 Update README.md 2018-02-28 16:23:30 +08:00
yincongcyincong
938ddd1141 Update README.md 2018-02-28 16:15:29 +08:00
yincongcyincong
68080539f7 Update README.md 2018-02-28 16:14:16 +08:00
yincongcyincong
5583b303be Update README.md 2018-02-28 16:11:43 +08:00
yincongcyincong
9301c9b49b Update README.md 2018-02-28 16:07:40 +08:00
yincongcyincong
8d2e210522 Update README.md 2018-02-28 16:05:47 +08:00
yincongcyincong
32661552ff Update README.md 2018-02-28 14:09:23 +08:00
arraykeys@gmail.com
9a111a59bf Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-02-28 13:40:14 +08:00
arraykeys@gmail.com
f5e472ea9f 4.3
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-02-28 13:27:13 +08:00
arraykeys@gmail.com
7599e2c793 add socks dns support 2018-02-27 18:33:22 +08:00
arraykeys@gmail.com
3726f5b9c3 update vendor
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-02-27 10:33:46 +08:00
arraykeys@gmail.com
dee517217e add dns support
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-02-26 18:44:14 +08:00
arraykeys@gmail.com
983912e44e Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-02-26 10:33:12 +08:00
arraykeys@gmail.com
982390f4b2 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-02-26 10:28:01 +08:00
arraykeys@gmail.com
5e3f51a8b0 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-02-26 10:26:58 +08:00
snail007
675ae276f9 Merge pull request #38 from snail007/dev
Update README.md
2018-02-20 17:41:29 +08:00
snail007
7899c45176 Update README.md 2018-02-20 17:40:53 +08:00
snail007
1dbb6feb57 Merge pull request #37 from snail007/dev
update readme
2018-02-13 12:46:38 +08:00
snail007
661685d136 Merge pull request #36 from yincongcyincong/dev
Update README.md
2018-02-13 12:44:40 +08:00
yincongcyincong
28947a0352 Update README.md 2018-02-13 08:20:18 +08:00
yincongcyincong
bd594684ce Update README.md 2018-02-13 08:18:40 +08:00
snail007
fda3609873 Merge pull request #35 from snail007/dev
Update README.md
2018-02-08 23:42:44 +08:00
snail007
d20db0c546 Update README.md 2018-02-08 23:42:04 +08:00
snail007
07fb22ae70 Merge pull request #34 from snail007/dev
add tg
2018-02-08 23:41:03 +08:00
snail007
8d5c3944ad Update README.md 2018-02-08 23:40:29 +08:00
snail007
7cf28aa9f4 Update README_ZH.md 2018-02-08 23:36:43 +08:00
snail007
731867b73c Merge pull request #33 from snail007/dev
Update README.md
2018-02-07 23:08:59 +08:00
snail007
1c6df2d9a2 Update README.md 2018-02-07 23:08:01 +08:00
snail007
ee5a248a39 Merge pull request #32 from snail007/dev
update docs
2018-02-07 23:05:53 +08:00
snail007
c174f85656 Merge pull request #31 from yincongcyincong/dev
Update README.md
2018-02-07 23:05:15 +08:00
yincongcyincong
2669aac7c9 Update README.md 2018-02-07 16:09:25 +08:00
snail007
f1aec74b11 Merge pull request #30 from snail007/dev
update en docs
2018-02-06 14:57:06 +08:00
snail007
1d382b2bf6 Merge pull request #29 from yincongcyincong/dev
Update README.md
2018-02-06 14:56:31 +08:00
yincongcyincong
55cac537b1 Update README.md 2018-02-06 14:50:31 +08:00
snail007
75258fa195 Merge pull request #28 from snail007/dev
update en_docs
2018-02-06 14:43:31 +08:00
snail007
cef2ca6d8e Merge pull request #27 from yincongcyincong/dev
Update README.md
2018-02-06 14:40:17 +08:00
yincongcyincong
4b1651bb3e Update README.md 2018-02-06 11:33:06 +08:00
snail007
78c116bca9 Merge pull request #26 from yincongcyincong/dev
en readme
2018-02-06 11:14:41 +08:00
yincongcyincong
a7c46f5582 Update README.md 2018-02-06 08:09:16 +08:00
yincongcyincong
f947d35bc3 Update README.md 2018-02-05 18:00:50 +08:00
yincongcyincong
a49e0166d4 Update README.md 2018-02-05 17:50:39 +08:00
yincongcyincong
7272b592d5 Update README.md 2018-02-05 11:35:50 +08:00
snail007
cced739d0e Update README_ZH.md 2018-02-05 10:57:07 +08:00
snail007
54ac46b3e4 Update README_ZH.md 2018-02-05 10:56:32 +08:00
snail007
8f9aa2fd64 Update README_ZH.md 2018-02-05 10:56:03 +08:00
snail007
c7b9cd5853 Update README_ZH.md 2018-02-05 10:55:28 +08:00
snail007
f9dfac55b0 Update README_ZH.md 2018-02-05 10:54:51 +08:00
snail007
b4ad1b5465 Update README_ZH.md 2018-02-05 10:54:11 +08:00
snail007
e2b2b7e255 Update README_ZH.md 2018-02-05 10:53:43 +08:00
snail007
80b691564c Update README_ZH.md 2018-02-05 10:52:25 +08:00
yincongcyincong
dfc326b771 Update README.md 2018-02-01 18:01:10 +08:00
yincongcyincong
7cfde70a9f Update README.md 2018-02-01 17:59:21 +08:00
arraykeys@gmail.com
20837ba983 Merge branch 'master' of https://github.com/snail007/goproxy.git 2018-02-01 15:26:08 +08:00
arraykeys@gmail.com
8dda32a599 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-02-01 15:07:26 +08:00
yincongcyincong
42ce2a4351 Update README.md 2018-01-31 12:57:11 +08:00
yincongcyincong
6574d5cd29 Update README.md 2018-01-31 12:41:52 +08:00
snail007
4e9ae9a8f5 Update README.md 2018-01-31 12:32:58 +08:00
arraykeys@gmail.com
a9ce3cf733 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-31 12:32:10 +08:00
snail007
65708a0f12 Update README.md 2018-01-31 12:31:29 +08:00
arraykeys@gmail.com
e2a3b5f9ee Merge branch 'x' into dev
# Conflicts:
#	README.md

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-01-31 12:27:58 +08:00
arraykeys@gmail.com
7a752537c5 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-31 12:27:25 +08:00
arraykeys@gmail.com
abd0b63fe9 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-31 12:15:27 +08:00
arraykeys@gmail.com
7ac7cd452b Merge branch 'endoc' into dev
# Conflicts:
#	install_auto.sh
#	main.go
#	release.sh

Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2018-01-31 12:13:34 +08:00
arraykeys@gmail.com
94cecdb8c0 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-31 12:13:01 +08:00
arraykeys@gmail.com
a8b35ba971 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-31 11:54:12 +08:00
arraykeys@gmail.com
bc1ab84b75 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-31 10:39:06 +08:00
snail007
acc895d2df Update README.md 2018-01-29 23:54:43 +08:00
snail007
23dbd0a92f Update README.md 2018-01-29 23:49:42 +08:00
snail007
c069b5cd97 Update README.md 2018-01-29 21:06:10 +08:00
snail007
f1dfe50d8b Update README.md 2018-01-29 21:02:52 +08:00
snail007
7d3820175f Update README.md 2018-01-29 21:00:55 +08:00
arraykeys@gmail.com
75032fdbb7 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-27 10:03:08 +08:00
arraykeys@gmail.com
23b3ad63cf Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-26 12:09:12 +08:00
arraykeys@gmail.com
7afd0c86cd Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-26 12:06:29 +08:00
arraykeys@gmail.com
5f0a341d22 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-26 11:03:34 +08:00
arraykeys@gmail.com
2a117376b7 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 17:20:28 +08:00
arraykeys@gmail.com
2fc750532d Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 17:19:54 +08:00
arraykeys@gmail.com
477be63cff Merge branch 'dev' 2018-01-24 16:55:49 +08:00
arraykeys@gmail.com
39b90357db Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 16:55:42 +08:00
arraykeys@gmail.com
2bd916eb73 Merge branch 'dev' 2018-01-24 16:52:27 +08:00
arraykeys@gmail.com
4d1b450b33 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 16:52:17 +08:00
arraykeys@gmail.com
cb70812cb7 Merge branch 'dev' 2018-01-24 16:50:43 +08:00
arraykeys@gmail.com
42e030e368 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 16:50:36 +08:00
arraykeys@gmail.com
f84cdc921d Merge branch 'dev' 2018-01-24 16:49:20 +08:00
arraykeys@gmail.com
02c07e7f4f Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 16:49:10 +08:00
arraykeys@gmail.com
d6ea190688 Merge branch 'dev' 2018-01-24 15:56:41 +08:00
arraykeys@gmail.com
b20487b928 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 15:56:32 +08:00
arraykeys@gmail.com
004cf5693f Merge branch 'dev' 2018-01-24 11:04:31 +08:00
arraykeys@gmail.com
ef8de6feb0 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 11:04:19 +08:00
arraykeys@gmail.com
d791ebe634 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 11:02:20 +08:00
arraykeys@gmail.com
5af4a1817e Merge branch 'master' of https://github.com/snail007/goproxy.git
# Conflicts:
#	README.md
2018-01-24 10:50:52 +08:00
arraykeys@gmail.com
e97b8c55f3 Merge branch 'dev' 2018-01-24 10:50:30 +08:00
arraykeys@gmail.com
99fcb76210 Merge branch 'dev' of https://github.com/snail007/goproxy.git into dev 2018-01-24 10:19:13 +08:00
arraykeys@gmail.com
d4fd34165e Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-24 10:18:37 +08:00
snail007
bd4741a0a0 Update README.md 2018-01-23 21:32:37 +08:00
snail007
5945c32646 Update README.md 2018-01-23 21:31:04 +08:00
snail007
9a9dc2594d Merge pull request #20 from wujunze/patch-1
修正文档参数
2018-01-15 18:05:45 +08:00
Panda
02547e9475 修正文档参数
修正文档参数
2018-01-15 16:31:08 +08:00
arraykeys@gmail.com
d81a823da1 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2018-01-15 15:18:36 +08:00
arraykeys@gmail.com
df74bcc885 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-20 17:07:29 +08:00
arraykeys@gmail.com
094bcebfa3 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-20 16:59:55 +08:00
arraykeys@gmail.com
bb2a16720b Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-18 10:52:21 +08:00
arraykeys@gmail.com
86d9a0c0f3 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-18 10:49:26 +08:00
arraykeys@gmail.com
5cf9d72ed3 add vendor
Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
2017-12-07 16:50:24 +08:00
arraykeys@gmail.com
801605676c Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-07 12:02:06 +08:00
arraykeys@gmail.com
a9dec75e59 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-05 17:49:58 +08:00
arraykeys@gmail.com
08d9d90fe1 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-05 11:57:05 +08:00
arraykeys@gmail.com
9749db9235 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-05 11:48:32 +08:00
arraykeys@gmail.com
11073aaaa5 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-05 11:46:15 +08:00
arraykeys@gmail.com
e35ddc4d53 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-05 11:18:40 +08:00
arraykeys@gmail.com
99b06e813e Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-05 10:27:51 +08:00
arraykeys@gmail.com
7164349944 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-04 11:07:23 +08:00
arraykeys@gmail.com
bf43b3adee Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-04 10:35:44 +08:00
arraykeys
6aa4b3c8a9 no message 2017-12-02 21:14:11 +08:00
arraykeys
7a9f7ef95e no message 2017-12-02 14:25:18 +08:00
arraykeys
ee1a9d3ec7 no message 2017-12-02 14:24:30 +08:00
arraykeys
6a69e58be5 no message 2017-12-02 14:22:14 +08:00
arraykeys
6e1d788677 no message 2017-12-02 14:21:21 +08:00
arraykeys
24f8f789c5 no message 2017-12-02 14:20:15 +08:00
arraykeys
2fb779f990 no message 2017-12-02 14:18:53 +08:00
arraykeys
0a9d3cd309 no message 2017-12-02 14:16:16 +08:00
arraykeys
977b1aba1c no message 2017-12-02 14:02:17 +08:00
arraykeys
a02aeeb906 no message 2017-12-01 23:52:21 +08:00
arraykeys
7e2e63137e no message 2017-12-01 22:14:07 +08:00
arraykeys@gmail.com
4b35219c27 Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-12-01 18:01:59 +08:00
arraykeys@gmail.com
0247c4701d Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-11-30 18:43:31 +08:00
arraykeys@gmail.com
e2cd0b8e4f Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com> 2017-11-30 16:50:47 +08:00
555 changed files with 87947 additions and 1827 deletions

3
.gitignore vendored
View File

@ -1,7 +1,10 @@
proxy
goproxy
*.exe
*.exe~
.*
*.prof
!.gitignore
release-*
proxy.crt
proxy.key

View File

@ -1,4 +1,97 @@
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版的内网穿透对应的子命令分别是serverclientbridge
使用方式和参数与之前的子命令tservertclienttserver完全一样另外serverclient增加了
压缩传输参数--c使用压缩传输速度更快。
v3.9
1.增加了守护运行参数--forever,比如: proxy http --forever ,
proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后重启子进程.

9
Dockerfile Normal file
View 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
View 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
View 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.

1230
README.md

File diff suppressed because it is too large Load Diff

1074
README_ZH.md Normal file

File diff suppressed because it is too large Load Diff

249
config.go
View File

@ -2,14 +2,20 @@ package main
import (
"bufio"
"crypto/sha1"
"fmt"
"log"
logger "log"
"os"
"os/exec"
"proxy/services"
"proxy/utils"
"runtime/pprof"
"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"
)
@ -17,6 +23,8 @@ var (
app *kingpin.Application
service *services.ServiceItem
cmd *exec.Cmd
cpuProfilingFile, memProfilingFile, blockProfilingFile, goroutineProfilingFile, threadcreateProfilingFile *os.File
isDebug bool
)
func initConfig() (err error) {
@ -34,8 +42,13 @@ func initConfig() (err error) {
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(APP_VERSION)
@ -43,10 +56,28 @@ func initConfig() (err error) {
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 := 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")
@ -59,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.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.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.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.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.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.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")
@ -82,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.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.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.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 := app.Command("udp", "proxy on udp mode")
@ -95,10 +126,41 @@ func initConfig() (err error) {
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.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.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()
@ -107,8 +169,7 @@ func initConfig() (err error) {
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()
tunnelServerArgs.Mux = tunnelServer.Flag("mux", "use multiplexing mode").Default("false").Bool()
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")
@ -117,7 +178,6 @@ func initConfig() (err error) {
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()
tunnelClientArgs.Mux = tunnelClient.Flag("mux", "use multiplexing mode").Default("false").Bool()
//########tunnel-bridge#########
tunnelBridge := app.Command("tbridge", "proxy on tunnel bridge mode")
@ -125,7 +185,6 @@ func initConfig() (err error) {
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()
tunnelBridgeArgs.Mux = tunnelBridge.Flag("mux", "use multiplexing mode").Default("false").Bool()
//########ssh#########
socks := app.Command("socks", "proxy on ssh mode")
@ -136,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.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()
@ -148,21 +208,116 @@ func initConfig() (err error) {
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.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.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", "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
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 |= log.Lshortfile | log.Lmicroseconds
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 |= log.Ltime
flags |= logger.Ltime
}
log.SetFlags(flags)
@ -234,7 +389,7 @@ func initConfig() (err error) {
log.Printf("ERR:%s,restarting...", err)
continue
}
log.Printf("%s [PID] %d unexpected exited, restarting...\n", os.Args[0], pid)
log.Printf("worker %s [PID] %d unexpected exited, restarting...\n", os.Args[0], pid)
time.Sleep(time.Second * 5)
}
}()
@ -242,16 +397,41 @@ func initConfig() (err error) {
}
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")
}
}
//regist services and run service
services.Regist("http", services.NewHTTP(), httpArgs)
services.Regist("tcp", services.NewTCP(), tcpArgs)
services.Regist("udp", services.NewUDP(), udpArgs)
services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs)
services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs)
services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs)
services.Regist("socks", services.NewSocks(), socksArgs)
service, err = services.Run(serviceName)
//regist services and run service
switch serviceName {
case "http":
services.Regist(serviceName, services.NewHTTP(), httpArgs, log)
case "tcp":
services.Regist(serviceName, services.NewTCP(), tcpArgs, log)
case "udp":
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 {
log.Fatalf("run service [%s] fail, ERR:%s", serviceName, err)
}
@ -270,3 +450,14 @@ func poster() {
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/images/2.1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/images/2.2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/images/5.2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/images/alipay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/images/fxdl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/images/http-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/images/http-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/images/http-kcp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/images/http-ssh-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/images/http-tls-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/images/http-tls-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/images/socks-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/images/socks-ssh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/images/socks-tls-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/images/socks-tls-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/images/sps-tls.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/images/tcp-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/images/tcp-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/images/tcp-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/images/tcp-tls-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/images/tcp-tls-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/images/udp-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/images/udp-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/images/udp-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/images/udp-tls-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/images/udp-tls-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/images/wxpay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

27
gui/README.md Normal file
View 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)

View File

@ -5,7 +5,7 @@ if [ -e /tmp/proxy ]; then
fi
mkdir /tmp/proxy
cd /tmp/proxy
wget https://github.com/snail007/goproxy/releases/download/v3.9/proxy-linux-amd64.tar.gz
wget https://github.com/snail007/goproxy/releases/download/v4.9/proxy-linux-amd64.tar.gz
# #install proxy
tar zxvf proxy-linux-amd64.tar.gz

View File

@ -4,11 +4,12 @@ import (
"log"
"os"
"os/signal"
"proxy/services"
"syscall"
"github.com/snail007/goproxy/services"
)
const APP_VERSION = "3.9"
const APP_VERSION = "4.9"
func main() {
err := initConfig()
@ -40,6 +41,9 @@ func Clean(s *services.Service) {
log.Printf("clean process %d", cmd.Process.Pid)
cmd.Process.Kill()
}
if isDebug {
saveProfiling()
}
cleanupDone <- true
}
}()

View File

@ -1,66 +1,71 @@
#!/bin/bash
VER="3.9"
VER="4.9"
RELEASE="release-${VER}"
rm -rf .cert
mkdir .cert
go build
go build -o proxy
cd .cert
../proxy keygen
cd ..
rm -rf ${RELEASE}
mkdir ${RELEASE}
export CGO_ENABLED=0
#linux
GOOS=linux GOARCH=386 go build && 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
GOOS=linux GOARCH=arm GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm.tar.gz" proxy direct blocked
GOOS=linux GOARCH=arm64 GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm64.tar.gz" proxy direct blocked
GOOS=linux GOARCH=mips go build && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy direct blocked
GOOS=linux GOARCH=mips64 go build && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy direct blocked
GOOS=linux GOARCH=mips64le go build && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy direct blocked
GOOS=linux GOARCH=mipsle go build && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy direct blocked
GOOS=linux GOARCH=ppc64 go build && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy direct blocked
GOOS=linux GOARCH=ppc64le go build && tar zcfv "${RELEASE}/proxy-linux-ppc64le.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=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-386.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
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
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
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
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
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
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
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o proxy && tar zcfv "${RELEASE}/proxy-linux-mips.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
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
GOOS=android GOARCH=386 go build && 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
GOOS=android GOARCH=arm go build && 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=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-android-386.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
CGO_ENABLED=0 GOOS=android GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-android-arm.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
GOOS=darwin GOARCH=386 go build go build && 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
GOOS=darwin GOARCH=arm go build && 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=386 go build -o proxy && tar zcfv "${RELEASE}/proxy-darwin-386.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
CGO_ENABLED=0 GOOS=darwin GOARCH=arm go build -o proxy && tar zcfv "${RELEASE}/proxy-darwin-arm.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
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
GOOS=freebsd GOARCH=386 go build && 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
GOOS=freebsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-freebsd-arm.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
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-freebsd-amd64.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
GOOS=nacl GOARCH=386 go build && 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
GOOS=nacl GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-nacl-arm.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
CGO_ENABLED=0 GOOS=nacl GOARCH=amd64p32 go build -o proxy && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.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
GOOS=netbsd GOARCH=386 go build && 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
GOOS=netbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-netbsd-arm.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
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-netbsd-amd64.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
GOOS=openbsd GOARCH=386 go build && 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
GOOS=openbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-openbsd-arm.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
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-openbsd-amd64.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
GOOS=plan9 GOARCH=386 go build && 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
GOOS=plan9 GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-plan9-arm.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
CGO_ENABLED=0 GOOS=plan9 GOARCH=amd64 go build -o proxy && tar zcfv "${RELEASE}/proxy-plan9-amd64.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
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
GOOS=windows GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
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 -ldflags="-H=windowsgui" -o proxy-wingui.exe
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
#1.release.sh VER="xxx"

3
sdk/CHANGELOG Normal file
View File

@ -0,0 +1,3 @@
v4.8
1.修复了多个服务同时开启日志,只会输出到最后一个日志文件的bug.
2.增加了获取sdk版本的Version()方法.

259
sdk/README.md Normal file
View 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
[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-android/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-android.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-android/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-android/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-android.svg?style=plastic)](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
[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-ios/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-ios.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-ios/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-ios/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-ios.svg?style=plastic)](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
[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-windows/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-windows.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-windows/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-windows/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-windows.svg?style=plastic)](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
[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-linux/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-linux.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-linux/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-linux/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-linux.svg?style=plastic)](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
[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-mac/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-mac.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-mac/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-mac/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-mac.svg?style=plastic)](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
View File

@ -0,0 +1,7 @@
*.jar
*.aar
*.tar.gz
ios
android
Proxy.framework

View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
proxy-sdk.dll
proxy-sdk.h
proxy-sdk.so
proxy-sdk.a
*.tar.gz
test.c

Binary file not shown.

View 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."

View 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."

View 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
View 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() {
}

View File

@ -1,6 +1,10 @@
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")
// t := tcp.Flag("tcp-timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int()
@ -20,6 +24,48 @@ const (
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 {
Parent *string
CertFile *string
@ -65,16 +111,16 @@ type TCPArgs struct {
ParentType *string
LocalType *string
Timeout *int
PoolSize *int
CheckParentInterval *int
KCPMethod *string
KCPKey *string
KCP kcpcfg.KCPConfigArgs
}
type HTTPArgs struct {
Parent *string
CertFile *string
KeyFile *string
CaCertFile *string
CaCertBytes []byte
CertBytes []byte
KeyBytes []byte
Local *string
@ -92,7 +138,6 @@ type HTTPArgs struct {
ParentType *string
LocalType *string
Timeout *int
PoolSize *int
CheckParentInterval *int
SSHKeyFile *string
SSHKeyFileSalt *string
@ -100,9 +145,14 @@ type HTTPArgs struct {
SSHUser *string
SSHKeyBytes []byte
SSHAuthMethod ssh.AuthMethod
KCPMethod *string
KCPKey *string
KCP kcpcfg.KCPConfigArgs
LocalIPS *[]string
DNSAddress *string
DNSTTL *int
LocalKey *string
ParentKey *string
LocalCompress *bool
ParentCompress *bool
}
type UDPArgs struct {
Parent *string
@ -113,7 +163,6 @@ type UDPArgs struct {
Local *string
ParentType *string
Timeout *int
PoolSize *int
CheckParentInterval *int
}
type SocksArgs struct {
@ -123,6 +172,8 @@ type SocksArgs struct {
LocalType *string
CertFile *string
KeyFile *string
CaCertFile *string
CaCertBytes []byte
CertBytes []byte
KeyBytes []byte
SSHKeyFile *string
@ -142,13 +193,60 @@ type SocksArgs struct {
AuthURLOkCode *int
AuthURLTimeout *int
AuthURLRetry *int
KCPMethod *string
KCPKey *string
KCP kcpcfg.KCPConfigArgs
UDPParent *string
UDPLocal *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 {
switch *a.LocalType {
case TYPE_TLS:

View File

@ -4,48 +4,71 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
logger "log"
"net"
"proxy/utils"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/snail007/goproxy/utils"
"github.com/snail007/goproxy/utils/conncrypt"
"golang.org/x/crypto/ssh"
)
type HTTP struct {
outPool utils.OutPool
outPool utils.OutConn
cfg HTTPArgs
checker utils.Checker
basicAuth utils.BasicAuth
sshClient *ssh.Client
lockChn chan bool
domainResolver utils.DomainResolver
isStop bool
serverChannels []*utils.ServerChannel
userConns utils.ConcurrentMap
log *logger.Logger
}
func NewHTTP() Service {
return &HTTP{
outPool: utils.OutPool{},
outPool: utils.OutConn{},
cfg: HTTPArgs{},
checker: utils.Checker{},
basicAuth: utils.BasicAuth{},
lockChn: make(chan bool, 1),
isStop: false,
serverChannels: []*utils.ServerChannel{},
userConns: utils.NewConcurrentMap(),
}
}
func (s *HTTP) CheckArgs() {
var err error
func (s *HTTP) CheckArgs() (err error) {
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" {
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.SSHUser == "" {
log.Fatalf("ssh user required")
err = fmt.Errorf("ssh user required")
return
}
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 != "" {
@ -54,7 +77,8 @@ func (s *HTTP) CheckArgs() {
var SSHSigner ssh.Signer
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
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 != "" {
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)
}
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)
}
}
return
}
func (s *HTTP) InitService() {
func (s *HTTP) InitService() (err error) {
s.InitBasicAuth()
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" {
err := s.ConnectSSH()
err = s.ConnectSSH()
if err != nil {
log.Fatalf("init service fail, ERR: %s", err)
err = fmt.Errorf("init service fail, ERR: %s", err)
return
}
go func() {
//循环检查ssh网络连通性
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 {
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = conn.Write([]byte{0})
conn.SetDeadline(time.Time{})
}
if err != nil {
if s.sshClient != nil {
@ -92,7 +127,7 @@ func (s *HTTP) InitService() {
s.sshClient.Conn.Close()
}
}
log.Printf("ssh offline, retrying...")
s.log.Printf("ssh offline, retrying...")
s.ConnectSSH()
} else {
conn.Close()
@ -101,34 +136,66 @@ func (s *HTTP) InitService() {
}
}()
}
return
}
func (s *HTTP) StopService() {
if s.outPool.Pool != nil {
s.outPool.Pool.ReleaseAll()
defer func() {
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.CheckArgs()
if err = s.CheckArgs(); err != nil {
return
}
if err = s.InitService(); err != nil {
return
}
if *s.cfg.Parent != "" {
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
s.log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
s.InitOutConnPool()
}
s.InitService()
host, port, _ := net.SplitHostPort(*s.cfg.Local)
for _, addr := range strings.Split(*s.cfg.Local, ",") {
if addr != "" {
host, port, _ := net.SplitHostPort(addr)
p, _ := strconv.Atoi(port)
sc := utils.NewServerChannel(host, p)
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.callback)
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.KCPMethod, *s.cfg.KCPKey, s.callback)
err = sc.ListenKCP(s.cfg.KCP, s.callback, s.log)
}
if err != nil {
return
}
log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
s.log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
s.serverChannels = append(s.serverChannels, &sc)
}
}
return
}
@ -138,45 +205,53 @@ func (s *HTTP) Clean() {
func (s *HTTP) callback(inConn net.Conn) {
defer func() {
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 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 != 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)
return
}
address := req.Host
useProxy := true
host, _, _ := net.SplitHostPort(address)
useProxy := false
if !utils.IsIternalIP(host, *s.cfg.Always) {
useProxy = true
if *s.cfg.Parent == "" {
useProxy = false
} else if *s.cfg.Always {
useProxy = true
} else {
if req.IsHTTPS() {
s.checker.Add(address, true, req.Method, "", nil)
} 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(req.Host)
//log.Printf("blocked ? : %v, %s , fail:%d ,success:%d", useProxy, address, n, m)
useProxy, _, _ = s.checker.IsBlocked(k)
//s.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)
if err != nil {
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 {
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)
}
@ -191,58 +266,75 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut
return
}
var outConn net.Conn
var _outConn interface{}
tryCount := 0
maxTryCount := 5
for {
if s.isStop {
return
}
if useProxy {
if *s.cfg.ParentType == "ssh" {
outConn, err = s.getSSHConn(address)
} else {
//log.Printf("%v", s.outPool)
_outConn, err = s.outPool.Pool.Get()
if err == nil {
outConn = _outConn.(net.Conn)
}
// s.log.Printf("%v", s.outPool)
outConn, err = s.outPool.Get()
}
} else {
outConn, err = utils.ConnectHost(address, *s.cfg.Timeout)
outConn, err = utils.ConnectHost(s.Resolve(address), *s.cfg.Timeout)
}
tryCount++
if err == nil || tryCount > maxTryCount {
break
} 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)
}
}
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)
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()
//outLocalAddr := outConn.LocalAddr().String()
if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") {
//https无上级或者上级非代理,proxy需要响应connect请求,并直连目标
err = req.HTTPSReply()
} else {
//https或者http,上级是代理,proxy需要转发
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 {
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)
return
}
}
utils.IoBind((*inConn), outConn, func(err interface{}) {
log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host)
})
log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, req.Host)
s.log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host)
s.userConns.Remove(inAddr)
}, 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
}
@ -250,7 +342,7 @@ func (s *HTTP) getSSHConn(host string) (outConn net.Conn, err interface{}) {
maxTryCount := 1
tryCount := 0
RETRY:
if tryCount >= maxTryCount {
if tryCount >= maxTryCount || s.isStop {
return
}
wait := make(chan bool, 1)
@ -269,7 +361,7 @@ RETRY:
err = fmt.Errorf("ssh dial %s timeout", host)
}
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()
if e == nil {
tryCount++
@ -299,7 +391,7 @@ func (s *HTTP) ConnectSSH() (err error) {
if s.sshClient != nil {
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
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 {
//dur int, isTLS bool, certBytes, keyBytes []byte,
//parent string, timeout int, InitialCap int, MaxCap int
s.outPool = utils.NewOutPool(
s.outPool = utils.NewOutConn(
*s.cfg.CheckParentInterval,
*s.cfg.ParentType,
*s.cfg.KCPMethod,
*s.cfg.KCPKey,
s.cfg.CertBytes, s.cfg.KeyBytes,
*s.cfg.Parent,
s.cfg.KCP,
s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes,
s.Resolve(*s.cfg.Parent),
*s.cfg.Timeout,
*s.cfg.PoolSize,
*s.cfg.PoolSize*2,
)
}
}
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 != "" {
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 != "" {
var n = 0
@ -333,11 +426,11 @@ func (s *HTTP) InitBasicAuth() (err error) {
err = fmt.Errorf("auth-file ERR:%s", err)
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 {
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
}
@ -355,7 +448,11 @@ func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
}
if inPort == outPort {
var outIPs []net.IP
if *s.cfg.DNSAddress != "" {
outIPs = []net.IP{net.ParseIP(s.Resolve(outDomain))}
} else {
outIPs, err = net.LookupIP(outDomain)
}
if err == nil {
for _, ip := range outIPs {
if ip.String() == inIP {
@ -379,3 +476,13 @@ func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
}
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
View 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
}

View File

@ -1 +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
}
}
}

View File

@ -1 +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)
}
}

View File

@ -1 +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)
}
}()
}

View File

@ -2,50 +2,62 @@ package services
import (
"fmt"
"log"
logger "log"
"runtime/debug"
)
type Service interface {
Start(args interface{}) (err error)
Start(args interface{}, log *logger.Logger) (err error)
Clean()
}
type ServiceItem struct {
S Service
Args interface{}
Name string
Log *logger.Logger
}
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{
S: s,
Args: args,
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]
if ok {
go func() {
defer func() {
err := recover()
if err != nil {
log.Fatalf("%s servcie crashed, ERR: %s\ntrace:%s", name, err, string(debug.Stack()))
e := recover()
if e != nil {
err = fmt.Errorf("%s servcie crashed, ERR: %s\ntrace:%s", name, e, string(debug.Stack()))
}
}()
if len(args) == 1 {
err = service.S.Start(args[0])
if args != nil {
err = service.S.Start(args, service.Log)
} else {
err = service.S.Start(service.Args)
err = service.S.Start(service.Args, service.Log)
}
if err != nil {
log.Fatalf("%s servcie fail, ERR: %s", name, err)
err = fmt.Errorf("%s servcie fail, ERR: %s", name, err)
}
}()
}
if !ok {
} else {
err = fmt.Errorf("service %s not found", name)
}
return

View File

@ -4,15 +4,17 @@ import (
"crypto/tls"
"fmt"
"io/ioutil"
"log"
logger "log"
"net"
"proxy/utils"
"proxy/utils/aes"
"proxy/utils/socks"
"runtime/debug"
"strings"
"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"
)
@ -23,6 +25,11 @@ type Socks struct {
sshClient *ssh.Client
lockChn chan bool
udpSC utils.ServerChannel
sc *utils.ServerChannel
domainResolver utils.DomainResolver
isStop bool
userConns utils.ConcurrentMap
log *logger.Logger
}
func NewSocks() Service {
@ -31,27 +38,50 @@ func NewSocks() Service {
checker: utils.Checker{},
basicAuth: utils.BasicAuth{},
lockChn: make(chan bool, 1),
isStop: false,
userConns: utils.NewConcurrentMap(),
}
}
func (s *Socks) CheckArgs() {
var err error
if *s.cfg.LocalType == "tls" {
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
func (s *Socks) CheckArgs() (err error) {
if *s.cfg.LocalType == "tls" || (*s.cfg.Parent != "" && *s.cfg.ParentType == "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
}
}
}
if *s.cfg.Parent != "" {
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" {
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
host, _, e := net.SplitHostPort(*s.cfg.Parent)
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.SSHUser == "" {
log.Fatalf("ssh user required")
err = fmt.Errorf("ssh user required")
return
}
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 != "" {
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
@ -59,7 +89,8 @@ func (s *Socks) CheckArgs() {
var SSHSigner ssh.Signer
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
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 != "" {
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)
}
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)
}
}
}
return
}
func (s *Socks) InitService() {
func (s *Socks) InitService() (err error) {
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" {
err := s.ConnectSSH()
if err != nil {
log.Fatalf("init service fail, ERR: %s", err)
e := s.ConnectSSH()
if e != nil {
err = fmt.Errorf("init service fail, ERR: %s", e)
return
}
go func() {
//循环检查ssh网络连通性
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 {
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = conn.Write([]byte{0})
conn.SetDeadline(time.Time{})
}
if err != nil {
if s.sshClient != nil {
s.sshClient.Close()
}
log.Printf("ssh offline, retrying...")
s.log.Printf("ssh offline, retrying...")
s.ConnectSSH()
} else {
conn.Close()
@ -104,45 +145,71 @@ func (s *Socks) InitService() {
}()
}
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 {
s.udpSC = utils.NewServerChannelHost(*s.cfg.UDPLocal)
err := s.udpSC.ListenUDP(s.udpCallback)
if err != nil {
log.Fatalf("init udp service fail, ERR: %s", err)
s.udpSC = utils.NewServerChannelHost(*s.cfg.UDPLocal, s.log)
e := s.udpSC.ListenUDP(s.udpCallback)
if e != nil {
err = fmt.Errorf("init udp service fail, ERR: %s", e)
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() {
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 {
s.sshClient.Close()
}
if s.udpSC.UDPListener != nil {
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()
s.cfg = args.(SocksArgs)
s.CheckArgs()
s.InitService()
if *s.cfg.Parent != "" {
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
if err = s.CheckArgs(); err != nil {
return
}
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 {
err = sc.ListenTCP(s.socksConnCallback)
} 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 {
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.socksConnCallback)
err = sc.ListenKCP(s.cfg.KCP, s.socksConnCallback, s.log)
}
if err != nil {
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
}
func (s *Socks) Clean() {
@ -158,29 +225,29 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
//decode b
rawB, err = goaes.Decrypt(s.UDPKey(), b)
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
}
}
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 {
log.Printf("parse udp packet fail, ERR:%s", err)
s.log.Printf("parse udp packet fail, ERR:%s", err)
return
}
//防止死循环
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
}
//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.ParentType == "tls" {
//encode b
rawB, err = goaes.Encrypt(s.UDPKey(), rawB)
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
}
}
@ -188,42 +255,45 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
if parent == "" {
parent = *s.cfg.Parent
}
dstAddr, err := net.ResolveUDPAddr("udp", parent)
dstAddr, err := net.ResolveUDPAddr("udp", s.Resolve(parent))
if err != nil {
log.Printf("can't resolve address: %s", err)
s.log.Printf("can't resolve address: %s", err)
return
}
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
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
}
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*5)))
_, 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 {
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()
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)
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
length, _, err := conn.ReadFromUDP(buf)
conn.SetDeadline(time.Time{})
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()
return
}
respBody := buf[0:length]
log.Printf("udp response:%v", len(respBody))
//log.Printf("revecived udp packet from %s", dstAddr.String())
s.log.Printf("udp response:%v", len(respBody))
//s.log.Printf("revecived udp packet from %s", dstAddr.String())
if *s.cfg.ParentType == "tls" {
//decode b
respBody, err = goaes.Decrypt(s.UDPKey(), respBody)
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()
return
}
@ -231,72 +301,94 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
if *s.cfg.LocalType == "tls" {
d, err := goaes.Encrypt(s.UDPKey(), respBody)
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()
return
}
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
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 {
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
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 {
//本地代理
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 {
log.Printf("can't resolve address: %s", err)
s.log.Printf("can't resolve address: %s", err)
return
}
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
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
}
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*3)))
_, 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 {
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()
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)
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
length, _, err := conn.ReadFromUDP(buf)
conn.SetDeadline(time.Time{})
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()
return
}
respBody := buf[0:length]
//封装来自真实服务器的数据,返回给访问者
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" {
d, err := goaes.Encrypt(s.UDPKey(), respPacket)
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()
return
}
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
s.udpSC.UDPListener.SetDeadline(time.Time{})
d = nil
} else {
s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
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) {
defer func() {
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()
}
}()
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
@ -306,7 +398,7 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
if err != nil {
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
utils.CloseConn(&inConn)
log.Printf("new methods request fail,ERR: %s", err)
s.log.Printf("new methods request fail,ERR: %s", err)
return
}
@ -314,29 +406,29 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
if !methodReq.Select(socks.Method_NO_AUTH) {
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
utils.CloseConn(&inConn)
log.Printf("none method found : Method_NO_AUTH")
s.log.Printf("none method found : Method_NO_AUTH")
return
}
//method select reply
err = methodReq.Reply(socks.Method_NO_AUTH)
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)
return
}
// log.Printf("% x", methodReq.Bytes())
// s.log.Printf("% x", methodReq.Bytes())
} else {
//auth
if !methodReq.Select(socks.Method_USER_PASS) {
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
utils.CloseConn(&inConn)
log.Printf("none method found : Method_USER_PASS")
s.log.Printf("none method found : Method_USER_PASS")
return
}
//method reply need auth
err = methodReq.Reply(socks.Method_USER_PASS)
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)
return
}
@ -352,13 +444,19 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
r := buf[:n]
user := string(r[2 : r[1]+2])
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
_addr := strings.Split(inConn.RemoteAddr().String(), ":")
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.SetDeadline(time.Time{})
} else {
inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
inConn.Write([]byte{0x01, 0x01})
inConn.SetDeadline(time.Time{})
utils.CloseConn(&inConn)
return
}
@ -367,7 +465,7 @@ func (s *Socks) socksConnCallback(inConn net.Conn) {
//request detail
request, err := socks.NewRequest(inConn)
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)
return
}
@ -395,7 +493,7 @@ func (s *Socks) proxyUDP(inConn *net.Conn, methodReq socks.MethodsRequest, reque
}
host, _, _ := net.SplitHostPort((*inConn).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))
}
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()) {
utils.CloseConn(inConn)
log.Printf("dead loop detected , %s", request.Host())
s.log.Printf("dead loop detected , %s", request.Host())
utils.CloseConn(inConn)
return
}
for {
if s.isStop {
return
}
if *s.cfg.Always {
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
} else {
if *s.cfg.Parent != "" {
s.checker.Add(request.Addr(), true, "", "", nil)
useProxy, _, _ = s.checker.IsBlocked(request.Addr())
host, _, _ := net.SplitHostPort(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 {
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
} else {
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
outConn, err = utils.ConnectHost(s.Resolve(request.Addr()), *s.cfg.Timeout)
}
} else {
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
outConn, err = utils.ConnectHost(s.Resolve(request.Addr()), *s.cfg.Timeout)
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 == "" {
break
} 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)
}
}
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)
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)
inAddr := (*inConn).RemoteAddr().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{}) {
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{}) {
switch *s.cfg.ParentType {
@ -461,50 +576,65 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n
case "tcp":
if *s.cfg.ParentType == "tls" {
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)
} 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 {
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 {
err = fmt.Errorf("connect fail,%s", err)
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 n int
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = outConn.Write(methodBytes)
outConn.SetDeadline(time.Time{})
if err != nil {
err = fmt.Errorf("write method fail,%s", err)
return
}
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = outConn.Read(buf)
outConn.SetDeadline(time.Time{})
if err != nil {
err = fmt.Errorf("read method reply fail,%s", err)
return
}
//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)
outConn.SetDeadline(time.Time{})
if err != nil {
err = fmt.Errorf("write req detail fail,%s", err)
return
}
outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = outConn.Read(buf)
outConn.SetDeadline(time.Time{})
if err != nil {
err = fmt.Errorf("read req reply fail,%s", err)
return
}
//result := buf[:n]
//log.Printf("result:%v", result)
//s.log.Printf("result:%v", result)
case "ssh":
maxTryCount := 1
tryCount := 0
RETRY:
if tryCount >= maxTryCount {
if tryCount >= maxTryCount || s.isStop {
return
}
wait := make(chan bool, 1)
@ -524,7 +654,7 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n
s.sshClient.Close()
}
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()
if e == nil {
tryCount++
@ -556,15 +686,19 @@ func (s *Socks) ConnectSSH() (err error) {
if s.sshClient != nil {
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
return
}
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 != "" {
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 != "" {
var n = 0
@ -573,11 +707,11 @@ func (s *Socks) InitBasicAuth() (err error) {
err = fmt.Errorf("auth-file ERR:%s", err)
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 {
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
}
@ -595,7 +729,11 @@ func (s *Socks) IsDeadLoop(inLocalAddr string, host string) bool {
}
if inPort == outPort {
var outIPs []net.IP
if *s.cfg.DNSAddress != "" {
outIPs = []net.IP{net.ParseIP(s.Resolve(outDomain))}
} else {
outIPs, err = net.LookupIP(outDomain)
}
if err == nil {
for _, ip := range outIPs {
if ip.String() == inIP {
@ -619,3 +757,13 @@ func (s *Socks) IsDeadLoop(inLocalAddr string, host string) bool {
}
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
View 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
}

View File

@ -4,66 +4,100 @@ import (
"bufio"
"fmt"
"io"
"log"
logger "log"
"net"
"proxy/utils"
"runtime/debug"
"time"
"github.com/snail007/goproxy/utils"
"strconv"
)
type TCP struct {
outPool utils.OutPool
outPool utils.OutConn
cfg TCPArgs
sc *utils.ServerChannel
isStop bool
userConns utils.ConcurrentMap
log *logger.Logger
}
func NewTCP() Service {
return &TCP{
outPool: utils.OutPool{},
outPool: utils.OutConn{},
cfg: TCPArgs{},
isStop: false,
userConns: utils.NewConcurrentMap(),
}
}
func (s *TCP) CheckArgs() {
func (s *TCP) CheckArgs() (err error) {
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 == "" {
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 {
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()
return
}
func (s *TCP) StopService() {
if s.outPool.Pool != nil {
s.outPool.Pool.ReleaseAll()
defer func() {
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.CheckArgs()
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
s.InitService()
if err = s.CheckArgs(); err != nil {
return
}
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)
p, _ := strconv.Atoi(port)
sc := utils.NewServerChannel(host, p)
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.callback)
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback)
} 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 {
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
}
@ -73,7 +107,7 @@ func (s *TCP) Clean() {
func (s *TCP) callback(inConn net.Conn) {
defer func() {
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
@ -90,19 +124,15 @@ func (s *TCP) callback(inConn net.Conn) {
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
}
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)
}
}
func (s *TCP) OutToTCP(inConn *net.Conn) (err error) {
var outConn net.Conn
var _outConn interface{}
_outConn, err = s.outPool.Pool.Get()
if err == nil {
outConn = _outConn.(net.Conn)
}
outConn, err = s.outPool.Get()
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)
return
}
@ -111,55 +141,64 @@ func (s *TCP) OutToTCP(inConn *net.Conn) (err error) {
outAddr := outConn.RemoteAddr().String()
//outLocalAddr := outConn.LocalAddr().String()
utils.IoBind((*inConn), outConn, func(err interface{}) {
log.Printf("conn %s - %s released", inAddr, outAddr)
})
log.Printf("conn %s - %s connected", inAddr, outAddr)
s.log.Printf("conn %s - %s released", inAddr, outAddr)
s.userConns.Remove(inAddr)
}, 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
}
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 {
if s.isStop {
(*inConn).Close()
return
}
srcAddr, body, err := utils.ReadUDPPacket(bufio.NewReader(*inConn))
if err == io.EOF || err == io.ErrUnexpectedEOF {
//log.Printf("connection %s released", srcAddr)
//s.log.Printf("connection %s released", srcAddr)
utils.CloseConn(inConn)
break
}
//log.Debugf("udp packet revecived:%s,%v", srcAddr, body)
dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent)
if err != nil {
log.Printf("can't resolve address: %s", err)
s.log.Printf("can't resolve address: %s", err)
utils.CloseConn(inConn)
break
}
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
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
}
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = conn.Write(body)
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
}
//log.Debugf("send udp packet to %s success", dstAddr.String())
buf := make([]byte, 512)
len, _, err := conn.ReadFromUDP(buf)
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
}
respBody := buf[0:len]
//log.Debugf("revecived udp packet from %s , %v", dstAddr.String(), respBody)
_, err = (*inConn).Write(utils.UDPPacket(srcAddr, respBody))
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)
break
}
//log.Printf("send udp response success ,from:%s", dstAddr.String())
//s.log.Printf("send udp response success ,from:%s", dstAddr.String())
}
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 {
//dur int, isTLS bool, certBytes, keyBytes []byte,
//parent string, timeout int, InitialCap int, MaxCap int
s.outPool = utils.NewOutPool(
s.outPool = utils.NewOutConn(
*s.cfg.CheckParentInterval,
*s.cfg.ParentType,
*s.cfg.KCPMethod,
*s.cfg.KCPKey,
s.cfg.CertBytes, s.cfg.KeyBytes,
s.cfg.KCP,
s.cfg.CertBytes, s.cfg.KeyBytes, nil,
*s.cfg.Parent,
*s.cfg.Timeout,
*s.cfg.PoolSize,
*s.cfg.PoolSize*2,
)
}
}

View File

@ -1,12 +1,18 @@
package services
import (
"bufio"
"log"
"bytes"
"fmt"
logger "log"
"net"
"proxy/utils"
"os"
"strconv"
"time"
"github.com/snail007/goproxy/utils"
//"github.com/xtaci/smux"
smux "github.com/hashicorp/yamux"
)
type ServerConn struct {
@ -17,8 +23,8 @@ type TunnelBridge struct {
cfg TunnelBridgeArgs
serverConns utils.ConcurrentMap
clientControlConns utils.ConcurrentMap
// cmServer utils.ConnManager
// cmClient utils.ConnManager
isStop bool
log *logger.Logger
}
func NewTunnelBridge() Service {
@ -26,40 +32,91 @@ func NewTunnelBridge() Service {
cfg: TunnelBridgeArgs{},
serverConns: utils.NewConcurrentMap(),
clientControlConns: utils.NewConcurrentMap(),
// cmServer: utils.NewConnManager(),
// cmClient: utils.NewConnManager(),
isStop: false,
}
}
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 == "" {
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() {
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.CheckArgs()
s.InitService()
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)
sc := utils.NewServerChannel(host, p, s.log)
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, func(inConn net.Conn) {
//log.Printf("connection from %s ", inConn.RemoteAddr())
reader := bufio.NewReader(inConn)
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback)
if err != nil {
return
}
s.log.Printf("proxy on tunnel bridge mode %s", (*sc.Listener).Addr())
return
}
func (s *TunnelBridge) Clean() {
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 {
log.Printf("read error,ERR:%s", err)
s.log.Printf("read error,ERR:%s", err)
return
}
switch connType {
@ -67,20 +124,23 @@ func (s *TunnelBridge) Start(args interface{}) (err error) {
var key, ID, clientLocalAddr, serverID string
err = utils.ReadPacketData(reader, &key, &ID, &clientLocalAddr, &serverID)
if err != nil {
log.Printf("read error,ERR:%s", err)
s.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)
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 {
log.Printf("client %s control conn not exists", key)
s.log.Printf("client %s control conn not exists", key)
time.Sleep(time.Second * 3)
continue
}
@ -88,7 +148,7 @@ func (s *TunnelBridge) Start(args interface{}) (err error) {
_, 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)
s.log.Printf("%s client control conn write signal fail, err: %s, retrying...", key, err)
time.Sleep(time.Second * 3)
continue
} else {
@ -100,15 +160,15 @@ func (s *TunnelBridge) Start(args interface{}) (err error) {
var key, ID, serverID string
err = utils.ReadPacketData(reader, &key, &ID, &serverID)
if err != nil {
log.Printf("read error,ERR:%s", err)
s.log.Printf("read error,ERR:%s", err)
return
}
log.Printf("client connection , key: %s , id: %s, server id:%s", key, ID, serverID)
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()
log.Printf("server conn %s exists", ID)
s.log.Printf("server conn %s exists", ID)
return
}
serverConn := serverConnItem.(ServerConn).Conn
@ -116,129 +176,24 @@ func (s *TunnelBridge) Start(args interface{}) (err error) {
s.serverConns.Remove(ID)
// s.cmClient.RemoveOne(key, ID)
// s.cmServer.RemoveOne(serverID, ID)
log.Printf("conn %s released", ID)
})
s.log.Printf("conn %s released", ID)
}, s.log)
// s.cmClient.Add(key, ID, &inConn)
log.Printf("conn %s created", ID)
s.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)
s.log.Printf("read error,ERR:%s", err)
return
}
log.Printf("client control connection, key: %s", key)
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)
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)
s.log.Printf("set client %s control conn", key)
}
})
if err != nil {
return
}
log.Printf("proxy on tunnel bridge mode %s", (*sc.Listener).Addr())
return
}
func (s *TunnelBridge) Clean() {
s.StopService()
}

View File

@ -4,117 +4,90 @@ import (
"crypto/tls"
"fmt"
"io"
"log"
logger "log"
"net"
"proxy/utils"
"os"
"time"
"github.com/snail007/goproxy/utils"
//"github.com/xtaci/smux"
smux "github.com/hashicorp/yamux"
)
type TunnelClient struct {
cfg TunnelClientArgs
// cm utils.ConnManager
ctrlConn net.Conn
isStop bool
userConns utils.ConcurrentMap
log *logger.Logger
}
func NewTunnelClient() Service {
return &TunnelClient{
cfg: TunnelClientArgs{},
// cm: utils.NewConnManager(),
userConns: utils.NewConcurrentMap(),
isStop: false,
}
}
func (s *TunnelClient) InitService() {
// s.InitHeartbeatDeamon()
func (s *TunnelClient) InitService() (err error) {
return
}
// func (s *TunnelClient) InitHeartbeatDeamon() {
// log.Printf("heartbeat started")
// go func() {
// var heartbeatConn net.Conn
// var ID = *s.cfg.Key
// for {
// //close all connection
// 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() {
func (s *TunnelClient) CheckArgs() (err error) {
if *s.cfg.Parent != "" {
log.Printf("use tls parent %s", *s.cfg.Parent)
s.log.Printf("use tls parent %s", *s.cfg.Parent)
} else {
log.Fatalf("parent required")
err = fmt.Errorf("parent required")
return
}
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() {
// 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.CheckArgs()
s.InitService()
log.Printf("proxy on tunnel client mode")
if err = s.CheckArgs(); err != nil {
return
}
if err = s.InitService(); err != nil {
return
}
s.log.Printf("proxy on tunnel client mode")
for {
//close all conn
// s.cm.Remove(*s.cfg.Key)
if s.isStop {
return
}
if s.ctrlConn != nil {
s.ctrlConn.Close()
}
s.ctrlConn, err = s.GetInConn(CONN_CLIENT_CONTROL, *s.cfg.Key)
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)
if s.ctrlConn != nil {
s.ctrlConn.Close()
@ -122,16 +95,19 @@ func (s *TunnelClient) Start(args interface{}) (err error) {
continue
}
for {
if s.isStop {
return
}
var ID, clientLocalAddr, serverID string
err = utils.ReadPacketData(s.ctrlConn, &ID, &clientLocalAddr, &serverID)
if err != nil {
if s.ctrlConn != nil {
s.ctrlConn.Close()
}
log.Printf("read connection signal err: %s, retrying...", err)
s.log.Printf("read connection signal err: %s, retrying...", err)
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]
localAddr := clientLocalAddr[4:]
if protocol == "udp" {
@ -161,9 +137,28 @@ func (s *TunnelClient) GetInConn(typ uint8, data ...string) (outConn net.Conn, e
}
func (s *TunnelClient) GetConn() (conn net.Conn, err error) {
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 {
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
}
@ -172,11 +167,17 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) {
var err error
// for {
for {
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)
if err != nil {
utils.CloseConn(&inConn)
log.Printf("connection err: %s, retrying...", err)
s.log.Printf("connection err: %s, retrying...", err)
time.Sleep(time.Second * 3)
continue
} else {
@ -184,18 +185,21 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) {
}
}
// s.cm.Add(*s.cfg.Key, ID, &inConn)
log.Printf("conn %s created", ID)
s.log.Printf("conn %s created", ID)
for {
if s.isStop {
return
}
srcAddr, body, err := utils.ReadUDPPacket(inConn)
if err == io.EOF || err == io.ErrUnexpectedEOF {
log.Printf("connection %s released", ID)
s.log.Printf("connection %s released", ID)
utils.CloseConn(&inConn)
break
} else if err != nil {
log.Printf("udp packet revecived fail, err: %s", err)
s.log.Printf("udp packet revecived fail, err: %s", err)
} 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)
}
@ -205,48 +209,51 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) {
func (s *TunnelClient) processUDPPacket(inConn *net.Conn, srcAddr, localAddr string, body []byte) {
dstAddr, err := net.ResolveUDPAddr("udp", localAddr)
if err != nil {
log.Printf("can't resolve address: %s", err)
s.log.Printf("can't resolve address: %s", err)
utils.CloseConn(inConn)
return
}
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
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
}
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = conn.Write(body)
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
}
//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)
length, _, err := conn.ReadFromUDP(buf)
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
}
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)
_, err = (*inConn).Write(bs)
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)
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) {
var inConn, outConn net.Conn
var err error
for {
if s.isStop {
return
}
inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID)
if err != nil {
utils.CloseConn(&inConn)
log.Printf("connection err: %s, retrying...", err)
s.log.Printf("connection err: %s, retrying...", err)
time.Sleep(time.Second * 3)
continue
} else {
@ -256,13 +263,16 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) {
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 {
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)
continue
}
@ -271,13 +281,17 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) {
if err != nil {
utils.CloseConn(&inConn)
utils.CloseConn(&outConn)
log.Printf("build connection error, err: %s", err)
s.log.Printf("build connection error, err: %s", err)
return
}
inAddr := inConn.RemoteAddr().String()
utils.IoBind(inConn, outConn, func(err interface{}) {
log.Printf("conn %s released", ID)
// s.cm.RemoveOne(*s.cfg.Key, ID)
})
// s.cm.Add(*s.cfg.Key, ID, &inConn)
log.Printf("conn %s created", ID)
s.log.Printf("conn %s released", ID)
s.userConns.Remove(inAddr)
}, s.log)
if c, ok := s.userConns.Get(inAddr); ok {
(*c.(*net.Conn)).Close()
}
s.userConns.Set(inAddr, &inConn)
s.log.Printf("conn %s created", ID)
}

View File

@ -4,27 +4,36 @@ import (
"crypto/tls"
"fmt"
"io"
"log"
logger "log"
"net"
"proxy/utils"
"os"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/snail007/goproxy/utils"
//"github.com/xtaci/smux"
smux "github.com/hashicorp/yamux"
)
type TunnelServer struct {
cfg TunnelServerArgs
udpChn chan UDPItem
sc utils.ServerChannel
isStop bool
udpConn *net.Conn
userConns utils.ConcurrentMap
log *logger.Logger
}
type TunnelServerManager struct {
cfg TunnelServerArgs
udpChn chan UDPItem
sc utils.ServerChannel
serverID string
// cm utils.ConnManager
servers []*Service
log *logger.Logger
}
func NewTunnelServerManager() Service {
@ -32,22 +41,28 @@ func NewTunnelServerManager() Service {
cfg: TunnelServerArgs{},
udpChn: make(chan UDPItem, 50000),
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.CheckArgs()
if err = s.CheckArgs(); err != nil {
return
}
if *s.cfg.Parent != "" {
log.Printf("use tls parent %s", *s.cfg.Parent)
s.log.Printf("use tls parent %s", *s.cfg.Parent)
} 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)
//log.Printf("route:%v", *s.cfg.Route)
s.log.Printf("server id: %s", s.serverID)
//s.log.Printf("route:%v", *s.cfg.Route)
for _, _info := range *s.cfg.Route {
IsUDP := *s.cfg.IsUDP
if strings.HasPrefix(_info, "udp://") {
@ -79,11 +94,12 @@ func (s *TunnelServerManager) Start(args interface{}) (err error) {
Key: &KEY,
Timeout: s.cfg.Timeout,
Mgr: s,
})
}, log)
if err != nil {
return
}
s.servers = append(s.servers, &server)
}
return
}
@ -91,82 +107,32 @@ func (s *TunnelServerManager) Clean() {
s.StopService()
}
func (s *TunnelServerManager) StopService() {
// s.cm.RemoveAll()
}
func (s *TunnelServerManager) CheckArgs() {
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
log.Fatalf("cert and key file required")
for _, server := range s.servers {
(*server).Clean()
}
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
}
func (s *TunnelServerManager) InitService() {
// s.InitHeartbeatDeamon()
func (s *TunnelServerManager) CheckArgs() (err error) {
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) InitService() (err error) {
return
}
// func (s *TunnelServerManager) InitHeartbeatDeamon() {
// log.Printf("heartbeat started")
// 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) {
outConn, err = s.GetConn()
if err != nil {
log.Printf("connection err: %s", err)
s.log.Printf("connection err: %s", err)
return
}
ID = s.serverID
_, err = outConn.Write(utils.BuildPacket(typ, s.serverID))
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)
return
}
@ -174,7 +140,7 @@ func (s *TunnelServerManager) GetOutConn(typ uint8) (outConn net.Conn, ID string
}
func (s *TunnelServerManager) GetConn() (conn net.Conn, err error) {
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 {
conn = net.Conn(&_conn)
}
@ -184,6 +150,8 @@ func NewTunnelServer() Service {
return &TunnelServer{
cfg: TunnelServerArgs{},
udpChn: make(chan UDPItem, 50000),
isStop: false,
userConns: utils.NewConcurrentMap(),
}
}
@ -193,22 +161,54 @@ type UDPItem struct {
srcAddr *net.UDPAddr
}
func (s *TunnelServer) InitService() {
s.UDPConnDeamon()
}
func (s *TunnelServer) CheckArgs() {
if *s.cfg.Remote == "" {
log.Fatalf("remote required")
func (s *TunnelServer) 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
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.CheckArgs()
s.InitService()
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.sc = utils.NewServerChannel(host, p, s.log)
if *s.cfg.IsUDP {
err = s.sc.ListenUDP(func(packet []byte, localAddr, srcAddr *net.UDPAddr) {
s.udpChn <- UDPItem{
@ -220,39 +220,45 @@ func (s *TunnelServer) Start(args interface{}) (err error) {
if err != nil {
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 {
err = s.sc.ListenTCP(func(inConn net.Conn) {
defer func() {
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 ID string
for {
if s.isStop {
return
}
outConn, ID, err = s.GetOutConn(CONN_SERVER)
if err != nil {
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)
continue
} else {
break
}
}
inAddr := inConn.RemoteAddr().String()
utils.IoBind(inConn, outConn, func(err interface{}) {
// s.cfg.Mgr.cm.RemoveOne(s.cfg.Mgr.serverID, ID)
log.Printf("%s conn %s released", *s.cfg.Key, ID)
})
//add conn
// s.cfg.Mgr.cm.Add(s.cfg.Mgr.serverID, ID, &inConn)
log.Printf("%s conn %s created", *s.cfg.Key, ID)
s.userConns.Remove(inAddr)
s.log.Printf("%s conn %s released", *s.cfg.Key, ID)
}, s.log)
if c, ok := s.userConns.Get(inAddr); ok {
(*c.(*net.Conn)).Close()
}
s.userConns.Set(inAddr, &inConn)
s.log.Printf("%s conn %s created", *s.cfg.Key, ID)
})
if err != nil {
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
}
@ -262,7 +268,7 @@ func (s *TunnelServer) Clean() {
func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err error) {
outConn, err = s.GetConn()
if err != nil {
log.Printf("connection err: %s", err)
s.log.Printf("connection err: %s", err)
return
}
remoteAddr := "tcp:" + *s.cfg.Remote
@ -272,7 +278,7 @@ func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err e
ID = utils.Uniqueid()
_, err = outConn.Write(utils.BuildPacket(typ, *s.cfg.Key, ID, remoteAddr, s.cfg.Mgr.serverID))
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)
return
}
@ -280,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) {
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 {
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
}
@ -290,7 +315,7 @@ func (s *TunnelServer) UDPConnDeamon() {
go func() {
defer func() {
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
@ -299,48 +324,60 @@ func (s *TunnelServer) UDPConnDeamon() {
// var cmdChn = make(chan bool, 1000)
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(CONN_SERVER)
if err != nil {
// cmdChn <- true
outConn = nil
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)
continue
} else {
go func(outConn net.Conn, ID string) {
go func() {
// <-cmdChn
// outConn.Close()
}()
if s.udpConn != nil {
(*s.udpConn).Close()
}
s.udpConn = &outConn
for {
if s.isStop {
return
}
srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn)
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
}
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
}
//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, ":")
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
}
port, _ := strconv.Atoi(_srcAddr[1])
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
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
}
//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)
break
@ -353,10 +390,10 @@ func (s *TunnelServer) UDPConnDeamon() {
if err != nil {
utils.CloseConn(&outConn)
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
}
//log.Printf("write packet %v", *item.packet)
//s.log.Printf("write packet %v", *item.packet)
}
}()
}

View File

@ -5,64 +5,92 @@ import (
"fmt"
"hash/crc32"
"io"
"log"
logger "log"
"net"
"proxy/utils"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/snail007/goproxy/services/kcpcfg"
"github.com/snail007/goproxy/utils"
)
type UDP struct {
p utils.ConcurrentMap
outPool utils.OutPool
outPool utils.OutConn
cfg UDPArgs
sc *utils.ServerChannel
isStop bool
log *logger.Logger
}
func NewUDP() Service {
return &UDP{
outPool: utils.OutPool{},
outPool: utils.OutConn{},
p: utils.NewConcurrentMap(),
isStop: false,
}
}
func (s *UDP) CheckArgs() {
func (s *UDP) CheckArgs() (err error) {
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 == "" {
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" {
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 {
s.InitOutConnPool()
}
return
}
func (s *UDP) StopService() {
if s.outPool.Pool != nil {
s.outPool.Pool.ReleaseAll()
defer func() {
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.CheckArgs()
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
s.InitService()
if err = s.CheckArgs(); err != nil {
return
}
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)
p, _ := strconv.Atoi(port)
sc := utils.NewServerChannel(host, p)
sc := utils.NewServerChannel(host, p, s.log)
s.sc = &sc
err = sc.ListenUDP(s.callback)
if err != nil {
return
}
log.Printf("udp proxy on %s", (*sc.UDPListener).LocalAddr())
s.log.Printf("udp proxy on %s", (*sc.UDPListener).LocalAddr())
return
}
@ -72,7 +100,7 @@ func (s *UDP) Clean() {
func (s *UDP) callback(packet []byte, localAddr, srcAddr *net.UDPAddr) {
defer func() {
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
@ -87,14 +115,14 @@ func (s *UDP) callback(packet []byte, localAddr, srcAddr *net.UDPAddr) {
err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType)
}
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) {
isNew = !s.p.Has(connKey)
var _conn interface{}
if isNew {
_conn, err = s.outPool.Pool.Get()
_conn, err = s.outPool.Get()
if err != nil {
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) {
numLocal := crc32.ChecksumIEEE([]byte(localAddr.String()))
numSrc := crc32.ChecksumIEEE([]byte(srcAddr.String()))
mod := uint32(*s.cfg.PoolSize)
mod := uint32(10)
if mod == 0 {
mod = 10
}
connKey := uint64((numLocal/10)*10 + numSrc%mod)
conn, isNew, err := s.GetConn(fmt.Sprintf("%d", connKey))
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
}
if isNew {
go func() {
defer func() {
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 {
if s.isStop {
conn.Close()
return
}
srcAddrFromConn, body, err := utils.ReadUDPPacket(bufio.NewReader(conn))
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))
break
}
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
}
//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, ":")
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
}
port, _ := strconv.Atoi(_srcAddr[1])
dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port}
_, err = s.sc.UDPListener.WriteToUDP(body, dstAddr)
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
}
//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)
//fmt.Println(conn, writer)
writer.Write(utils.UDPPacket(srcAddr.String(), packet))
err = writer.Flush()
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
}
//log.Printf("write packet %v", packet)
//s.log.Printf("write packet %v", packet)
return
}
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)
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
}
clientSrcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
conn, err := net.DialUDP("udp", clientSrcAddr, dstAddr)
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
}
conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout)))
_, err = conn.Write(packet)
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
}
//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)
len, _, err := conn.ReadFromUDP(buf)
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
}
//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)
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
}
//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
}
func (s *UDP) InitOutConnPool() {
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP {
//dur int, isTLS bool, certBytes, keyBytes []byte,
//parent string, timeout int, InitialCap int, MaxCap int
s.outPool = utils.NewOutPool(
s.outPool = utils.NewOutConn(
*s.cfg.CheckParentInterval,
*s.cfg.ParentType,
"", "",
s.cfg.CertBytes, s.cfg.KeyBytes,
kcpcfg.KCPConfigArgs{},
s.cfg.CertBytes, s.cfg.KeyBytes, nil,
*s.cfg.Parent,
*s.cfg.Timeout,
*s.cfg.PoolSize,
*s.cfg.PoolSize*2,
)
}
}

View File

@ -1,4 +1,3 @@
#!/bin/bash
rm -rf /usr/bin/proxy
rm -rf /usr/bin/proxyd
echo "uninstall done"

View 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)
}

View File

@ -7,27 +7,32 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
logger "log"
"math/rand"
"net"
"net/http"
"os"
"os/exec"
"github.com/snail007/goproxy/services/kcpcfg"
"golang.org/x/crypto/pbkdf2"
"strconv"
"strings"
"time"
"github.com/snail007/goproxy/utils/id"
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() {
defer func() {
if err := recover(); err != nil {
@ -65,11 +70,14 @@ func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interfac
}
src.Close()
dst.Close()
if fn != nil {
fn(err)
}
}()
}
func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) {
buf := make([]byte, 32*1024)
buf := LeakyBuffer.Get()
defer LeakyBuffer.Put(buf)
n := 0
for {
n, err = src.Read(buf)
@ -83,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, ":")
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) {
conf, err := getRequestTlsConfig(certBytes, keyBytes)
func TlsConnect(host string, port, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) {
conf, err := getRequestTlsConfig(certBytes, keyBytes, caCertBytes)
if err != nil {
return
}
@ -100,22 +108,49 @@ func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (con
}
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
cert, err = tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return
}
serverCertPool := x509.NewCertPool()
ok := serverCertPool.AppendCertsFromPEM(certBytes)
caBytes := certBytes
if caCertBytes != nil {
caBytes = caCertBytes
}
ok := serverCertPool.AppendCertsFromPEM(caBytes)
if !ok {
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{
RootCAs: serverCertPool,
Certificates: []tls.Certificate{cert},
ServerName: "proxy",
InsecureSkipVerify: false,
InsecureSkipVerify: true,
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
}
@ -124,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)
return
}
func ConnectKCPHost(hostAndPort, method, key string) (conn net.Conn, err error) {
kcpconn, err := kcp.DialWithOptions(hostAndPort, GetKCPBlock(method, key), 10, 3)
func ConnectKCPHost(hostAndPort string, config kcpcfg.KCPConfigArgs) (conn net.Conn, err error) {
kcpconn, err := kcp.DialWithOptions(hostAndPort, config.Block, *config.DataShard, *config.ParityShard)
if err != nil {
return
}
kcpconn.SetNoDelay(1, 10, 2, 1)
kcpconn.SetWindowSize(1024, 1024)
kcpconn.SetMtu(1400)
kcpconn.SetACKNoDelay(false)
kcpconn.SetStreamMode(true)
kcpconn.SetWriteDelay(true)
kcpconn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion)
kcpconn.SetMtu(*config.MTU)
kcpconn.SetWindowSize(*config.SndWnd, *config.RcvWnd)
kcpconn.SetACKNoDelay(*config.AckNodelay)
if *config.NoComp {
return kcpconn, err
}
return NewCompStream(kcpconn), err
}
func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) {
var cert tls.Certificate
cert, err = tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
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 {
_, err := os.Stat(_path)
if err != nil && os.IsNotExist(err) {
@ -193,20 +211,95 @@ func CloseConn(conn *net.Conn) {
}
}
func Keygen() (err error) {
cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048")
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("err:%s", err)
return
}
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`)
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"}
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"}
C := CList[int(RandInt(4))%len(CList)]
ST := RandString(int(RandInt(4) % 10))
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 {
log.Printf("err:%s", err)
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))
}
return
}
func GetAllInterfaceAddr() ([]net.IP, error) {
@ -301,9 +394,33 @@ func ReadUDPPacket(_reader io.Reader) (srcAddr string, packet []byte, err error)
return
}
func Uniqueid() string {
var src = rand.NewSource(time.Now().UnixNano())
s := fmt.Sprintf("%d", src.Int63())
return s[len(s)-5:len(s)-1] + fmt.Sprintf("%d", uint64(time.Now().UnixNano()))[8:]
return xid.New().String()
// var src = rand.NewSource(time.Now().UnixNano())
// 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) {
var len uint16
@ -385,15 +502,15 @@ func SubBytes(bytes []byte, start, end int) []byte {
}
return bytes[start:end]
}
func TlsBytes(cert, key string) (certBytes, keyBytes []byte) {
certBytes, err := ioutil.ReadFile(cert)
func TlsBytes(cert, key string) (certBytes, keyBytes []byte, err error) {
certBytes, err = ioutil.ReadFile(cert)
if err != nil {
log.Fatalf("err : %s", err)
err = fmt.Errorf("err : %s", err)
return
}
keyBytes, err = ioutil.ReadFile(key)
if err != nil {
log.Fatalf("err : %s", err)
err = fmt.Errorf("err : %s", err)
return
}
return
@ -430,7 +547,7 @@ func GetKCPBlock(method, key string) (block kcp.BlockCrypt) {
}
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 client *http.Client
conf := &tls.Config{
@ -444,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}
}
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 {
return
}
@ -453,6 +579,90 @@ func HttpGet(URL string, timeout int) (body []byte, code int, err error) {
body, err = ioutil.ReadAll(resp.Body)
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 {
// family uint16

264
utils/id/xid.go Normal file
View 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
View 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
}

View File

@ -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)
}

View File

@ -1,12 +1,17 @@
package utils
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log"
logger "log"
"net"
"runtime/debug"
"strconv"
"github.com/snail007/goproxy/services/kcpcfg"
kcp "github.com/xtaci/kcp-go"
)
@ -16,23 +21,26 @@ type ServerChannel struct {
Listener *net.Listener
UDPListener *net.UDPConn
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{
ip: ip,
port: port,
log: log,
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)
p, _ := strconv.Atoi(port)
return ServerChannel{
ip: h,
port: p,
log: log,
errAcceptHandler: func(err error) {
log.Printf("accept error , ERR:%s", err)
},
@ -41,13 +49,13 @@ func NewServerChannelHost(host string) ServerChannel {
func (sc *ServerChannel) SetErrAcceptHandler(fn func(err error)) {
sc.errAcceptHandler = fn
}
func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.Conn)) (err error) {
sc.Listener, err = ListenTls(sc.ip, sc.port, certBytes, keyBytes)
func (sc *ServerChannel) ListenTls(certBytes, keyBytes, caCertBytes []byte, fn func(conn net.Conn)) (err error) {
sc.Listener, err = sc.listenTls(sc.ip, sc.port, certBytes, keyBytes, caCertBytes)
if err == nil {
go func() {
defer func() {
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 {
@ -57,7 +65,7 @@ func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.
go func() {
defer func() {
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)
@ -72,7 +80,33 @@ func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.
}
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) {
var l net.Listener
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() {
defer func() {
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 {
@ -91,7 +125,7 @@ func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) {
go func() {
defer func() {
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)
@ -113,7 +147,7 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne
go func() {
defer func() {
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 {
@ -124,7 +158,7 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne
go func() {
defer func() {
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)
@ -138,28 +172,51 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne
}
return
}
func (sc *ServerChannel) ListenKCP(method, key string, fn func(conn net.Conn)) (err error) {
var l net.Listener
l, err = kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), GetKCPBlock(method, key), 10, 3)
func (sc *ServerChannel) ListenKCP(config kcpcfg.KCPConfigArgs, fn func(conn net.Conn), log *logger.Logger) (err error) {
lis, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), config.Block, *config.DataShard, *config.ParityShard)
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() {
defer func() {
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 {
var conn net.Conn
conn, err = (*sc.Listener).Accept()
//var conn net.Conn
conn, err := lis.AcceptKCP()
if err == nil {
go func() {
defer func() {
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()))
}
}()
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 {
sc.errAcceptHandler(err)

173
utils/sni/sni.go Normal file
View 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
View 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
View 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
}

View File

@ -3,44 +3,13 @@ package socks
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"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 {
ver uint8
cmd uint8
@ -53,20 +22,24 @@ type Request struct {
rw io.ReadWriter
}
func NewRequest(rw io.ReadWriter) (req Request, err interface{}) {
var b [1024]byte
func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{}) {
var b = make([]byte, 1024)
var n int
req = Request{rw: rw}
if header != nil && len(header) == 1 && len(header[0]) > 1 {
b = header[0]
n = len(header[0])
} 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.cmd = uint8(b[1])
req.reserve = uint8(b[2])
req.addressType = uint8(b[3])
if b[0] != 0x5 {
err = fmt.Errorf("sosck version supported")
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[8], ipv6[9], ipv6[10], ipv6[11],
)
if ipv6 != nil && "0000000000255255" != zeroiIPv6 {
if ipb == nil && ipv6 != nil && "0000000000255255" != zeroiIPv6 {
atyp = ATYP_IPV6
ipb = ip.To16()
}
@ -150,7 +123,7 @@ type MethodsRequest struct {
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() {
if err == nil {
err = recover()
@ -160,10 +133,15 @@ func NewMethodsRequest(r io.ReadWriter) (s MethodsRequest, err interface{}) {
s.rw = &r
var buf = make([]byte, 300)
var n int
if header != nil && len(header) == 1 && len(header[0]) > 1 {
buf = header[0]
n = len(header[0])
} else {
n, err = r.Read(buf)
if err != nil {
return
}
}
if buf[0] != 0x05 {
err = fmt.Errorf("socks version not supported")
return
@ -172,7 +150,6 @@ func NewMethodsRequest(r io.ReadWriter) (s MethodsRequest, err interface{}) {
err = fmt.Errorf("socks methods data length error")
return
}
s.ver = buf[0]
s.methodsCount = buf[1]
s.methods = buf[2:n]
@ -185,6 +162,9 @@ func (s *MethodsRequest) Version() uint8 {
func (s *MethodsRequest) MethodsCount() uint8 {
return s.methodsCount
}
func (s *MethodsRequest) Methods() []uint8 {
return s.methods
}
func (s *MethodsRequest) Select(method uint8) bool {
for _, m := range s.methods {
if m == method {
@ -201,17 +181,6 @@ func (s *MethodsRequest) Bytes() []byte {
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) {
p = UDPPacket{}
p.frag = uint8(b[2])
@ -239,6 +208,18 @@ func ParseUDPPacket(b []byte) (p UDPPacket, err error) {
p.header = b[:portIndex+2]
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 {
return s.header
}
@ -258,3 +239,104 @@ func (s *UDPPacket) Port() string {
func (s *UDPPacket) Data() []byte {
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
}

View File

@ -1,19 +1,26 @@
package utils
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
logger "log"
"net"
"net/url"
"strings"
"sync"
"time"
"github.com/snail007/goproxy/services/kcpcfg"
"github.com/snail007/goproxy/utils/sni"
"github.com/golang/snappy"
"github.com/miekg/dns"
)
type Checker struct {
@ -22,6 +29,8 @@ type Checker struct {
directMap ConcurrentMap
interval int64
timeout int
isStop bool
log *logger.Logger
}
type CheckerItem struct {
IsHTTPS bool
@ -37,11 +46,13 @@ type CheckerItem struct {
//NewChecker args:
//timeout : tcp timeout milliseconds ,connect to host
//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{
data: NewConcurrentMap(),
interval: interval,
timeout: timeout,
isStop: false,
log: log,
}
ch.blockedMap = ch.loadMap(blockedFile)
ch.directMap = ch.loadMap(directFile)
@ -51,7 +62,10 @@ func NewChecker(timeout int, interval int64, blockedFile, directFile string) Che
if !ch.directMap.IsEmpty() {
log.Printf("direct file loaded , domains : %d", ch.directMap.Count())
}
if interval > 0 {
ch.start()
}
return ch
}
@ -60,7 +74,7 @@ func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) {
if PathExists(f) {
_contents, err := ioutil.ReadFile(f)
if err != nil {
log.Printf("load file err:%s", err)
c.log.Printf("load file err:%s", err)
return
}
for _, line := range strings.Split(string(_contents), "\n") {
@ -72,24 +86,25 @@ func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) {
}
return
}
func (c *Checker) Stop() {
c.isStop = true
}
func (c *Checker) start() {
go func() {
//log.Printf("checker started")
for {
//log.Printf("checker did")
for _, v := range c.data.Items() {
go func(item CheckerItem) {
if c.isNeedCheck(item) {
//log.Printf("check %s", item.Domain)
//log.Printf("check %s", item.Host)
var conn net.Conn
var err error
if item.IsHTTPS {
conn, err = ConnectHost(item.Host, c.timeout)
if err == nil {
conn.SetDeadline(time.Now().Add(time.Millisecond))
conn.Close()
}
} else {
err = HTTPGet(item.URL, c.timeout)
}
if err != nil {
item.FailCount = item.FailCount + 1
} else {
@ -100,6 +115,9 @@ func (c *Checker) start() {
}(v.(CheckerItem))
}
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 {
u, err := url.Parse("http://" + address)
if err != nil {
log.Printf("blocked check , url parse err:%s", err)
c.log.Printf("blocked check , url parse err:%s", err)
return true
}
domainSlice := strings.Split(u.Hostname(), ".")
@ -155,22 +173,13 @@ func (c *Checker) domainIsInMap(address string, blockedMap bool) bool {
}
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) {
return
}
if !isHTTPS && strings.ToLower(method) != "get" {
return
}
var item CheckerItem
u := strings.Split(address, ":")
item = CheckerItem{
URL: URL,
Domain: u[0],
Host: address,
Data: data,
IsHTTPS: isHTTPS,
Method: method,
}
c.data.SetIfAbsent(item.Host, item)
}
@ -181,11 +190,15 @@ type BasicAuth struct {
authOkCode int
authTimeout int
authRetry int
dns *DomainResolver
log *logger.Logger
}
func NewBasicAuth() BasicAuth {
func NewBasicAuth(dns *DomainResolver, log *logger.Logger) BasicAuth {
return BasicAuth{
data: NewConcurrentMap(),
dns: dns,
log: log,
}
}
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 {
return true
}
log.Printf("%s", err)
ba.log.Printf("%s", err)
}
return false
}
@ -249,18 +262,27 @@ func (ba *BasicAuth) checkFromURL(userpass, ip, target string) (err error) {
if len(u) != 2 {
return
}
URL := ba.authURL
if strings.Contains(URL, "?") {
URL += "&"
} else {
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 tryCount = 0
var body []byte
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 {
break
} else if err != nil {
@ -271,10 +293,14 @@ func (ba *BasicAuth) checkFromURL(userpass, ip, target string) (err error) {
} else {
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 {
log.Print(err)
ba.log.Print(err)
time.Sleep(time.Second * 2)
}
tryCount++
@ -300,15 +326,21 @@ type HTTPRequest struct {
hostOrURL string
isBasicAuth bool
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)
len := 0
n := 0
req = HTTPRequest{
conn: inConn,
log: log,
}
len, err = (*inConn).Read(buf[:])
if header != nil && len(header) == 1 && len(header[0]) > 1 {
buf = header[0]
n = len(header[0])
} else {
n, err = (*inConn).Read(buf[:])
if err != nil {
if err != io.EOF {
err = fmt.Errorf("http decoder read err:%s", err)
@ -316,7 +348,18 @@ func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *
CloseConn(inConn)
return
}
req.HeadBuf = buf[:len]
}
req.HeadBuf = buf[:n]
//fmt.Println(string(req.HeadBuf))
//try sni
serverName, err0 := sni.ServerNameFromBytes(req.HeadBuf)
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))
@ -324,6 +367,7 @@ func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *
return
}
fmt.Sscanf(string(req.HeadBuf[:index]), "%s%s", &req.Method, &req.hostOrURL)
}
if req.Method == "" || req.hostOrURL == "" {
err = fmt.Errorf("http decoder data err:%s", SubStr(string(req.HeadBuf), 0, 50))
CloseConn(inConn)
@ -348,8 +392,7 @@ func (req *HTTPRequest) HTTP() (err error) {
return
}
}
req.URL, err = req.getHTTPURL()
if err == nil {
req.URL = req.getHTTPURL()
var u *url.URL
u, err = url.Parse(req.URL)
if err != nil {
@ -357,13 +400,17 @@ func (req *HTTPRequest) HTTP() (err error) {
}
req.Host = u.Host
req.addPortIfNot()
}
return
}
func (req *HTTPRequest) HTTPS() (err error) {
if req.isBasicAuth {
err = req.BasicAuth()
if err != nil {
return
}
}
req.Host = req.hostOrURL
req.addPortIfNot()
//_, err = fmt.Fprint(*req.conn, "HTTP/1.1 200 Connection established\r\n\r\n")
return
}
func (req *HTTPRequest) HTTPSReply() (err error) {
@ -374,24 +421,18 @@ func (req *HTTPRequest) IsHTTPS() bool {
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, err := req.getHeader("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
}
authorization = strings.Trim(authorization, " \r\n\t")
if 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")
fmt.Fprintf((*req.conn), "HTTP/1.1 %s Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"\"\r\n\r\nProxy Authentication Required", "407")
CloseConn(req.conn)
err = errors.New("require auth header data")
return
}
}
//log.Printf("Authorization:%s", authorization)
//log.Printf("Authorization:%authorization = req.getHeader("Authorization")
basic := strings.Fields(authorization)
if len(basic) != 2 {
err = fmt.Errorf("authorization data error,ERR:%s", authorization)
@ -404,43 +445,51 @@ func (req *HTTPRequest) BasicAuth() (err error) {
CloseConn(req.conn)
return
}
basicInfo = string(user)
return
}
func (req *HTTPRequest) BasicAuth() (err error) {
addr := strings.Split((*req.conn).RemoteAddr().String(), ":")
URL := ""
if req.IsHTTPS() {
URL = "https://" + req.Host
} else {
URL, _ = req.getHTTPURL()
URL = req.getHTTPURL()
}
user, err := req.GetAuthDataStr()
if err != nil {
return
}
authOk := (*req.basicAuth).Check(string(user), addr[0], URL)
//log.Printf("auth %s,%v", string(user), 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)
err = fmt.Errorf("basic auth fail")
return
}
return
}
func (req *HTTPRequest) getHTTPURL() (URL string, err error) {
func (req *HTTPRequest) getHTTPURL() (URL string) {
if !strings.HasPrefix(req.hostOrURL, "/") {
return req.hostOrURL, nil
return req.hostOrURL
}
_host, err := req.getHeader("host")
if err != nil {
_host := req.getHeader("host")
if _host == "" {
return
}
URL = fmt.Sprintf("http://%s%s", _host, req.hostOrURL)
return
}
func (req *HTTPRequest) getHeader(key string) (val string, err error) {
func (req *HTTPRequest) getHeader(key string) (val string) {
key = strings.ToUpper(key)
lines := strings.Split(string(req.HeadBuf), "\r\n")
//log.Println(lines)
for _, line := range lines {
line := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2)
if len(line) == 2 {
k := strings.ToUpper(strings.Trim(line[0], " "))
v := strings.Trim(line[1], " ")
hline := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2)
if len(hline) == 2 {
k := strings.ToUpper(strings.Trim(hline[0], " "))
v := strings.Trim(hline[1], " ")
if key == k {
val = v
return
@ -464,252 +513,55 @@ func (req *HTTPRequest) addPortIfNot() (newHost string) {
return
}
type OutPool struct {
Pool ConnPool
type OutConn struct {
dur int
typ string
certBytes []byte
keyBytes []byte
kcpMethod string
kcpKey string
caCertBytes []byte
kcp kcpcfg.KCPConfigArgs
address string
timeout int
}
func NewOutPool(dur int, typ, kcpMethod, kcpKey string, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) {
op = OutPool{
func NewOutConn(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int) (op OutConn) {
return OutConn{
dur: dur,
typ: typ,
certBytes: certBytes,
keyBytes: keyBytes,
kcpMethod: kcpMethod,
kcpKey: kcpKey,
caCertBytes: caCertBytes,
kcp: kcp,
address: address,
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" {
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 {
conn = net.Conn(&_conn)
}
} else if op.typ == "kcp" {
conn, err = ConnectKCPHost(op.address, op.kcpMethod, op.kcpKey)
conn, err = ConnectKCPHost(op.address, op.kcp)
} else {
conn, err = ConnectHost(op.address, op.timeout)
}
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 {
pool ConcurrentMap
l *sync.Mutex
log *logger.Logger
}
func NewConnManager() ConnManager {
func NewConnManager(log *logger.Logger) ConnManager {
cm := ConnManager{
pool: NewConcurrentMap(),
l: &sync.Mutex{},
log: log,
}
return cm
}
@ -726,7 +578,7 @@ func (cm *ConnManager) Add(key, ID string, conn *net.Conn) {
(*v.(*net.Conn)).Close()
}
conns.Set(ID, conn)
log.Printf("%s conn added", key)
cm.log.Printf("%s conn added", key)
return conns
})
}
@ -737,7 +589,7 @@ func (cm *ConnManager) Remove(key string) {
conns.IterCb(func(key string, v interface{}) {
CloseConn(v.(*net.Conn))
})
log.Printf("%s conns closed", key)
cm.log.Printf("%s conns closed", key)
}
cm.pool.Remove(key)
}
@ -752,7 +604,7 @@ func (cm *ConnManager) RemoveOne(key string, ID string) {
(*v.(*net.Conn)).Close()
conns.Remove(ID)
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)
}
}
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
View 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
View 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
View 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
View 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(&regs[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(&regs[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(&regs[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

File diff suppressed because it is too large Load Diff

1180
vendor/github.com/Yawning/chacha20/chacha20_amd64.s generated vendored Normal file

File diff suppressed because it is too large Load Diff

394
vendor/github.com/Yawning/chacha20/chacha20_ref.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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("&#34;") // shorter than "&quot;"
htmlApos = []byte("&#39;") // shorter than "&apos;" and apos was not in HTML until HTML5
htmlAmp = []byte("&amp;")
htmlLt = []byte("&lt;")
htmlGt = []byte("&gt;")
)
// 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

13
vendor/github.com/alecthomas/units/doc.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Package units provides helpful unit multipliers and functions for Go.
//
// The goal of this package is to have functionality similar to the time [1] package.
//
//
// [1] http://golang.org/pkg/time/
//
// It allows for code like this:
//
// n, err := ParseBase2Bytes("1KB")
// // n == 1024
// n = units.Mebibyte * 512
package units

Some files were not shown because too many files have changed in this diff Show More