Compare commits
264 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dda32a599 | ||
|
|
a9ce3cf733 | ||
|
|
e2a3b5f9ee | ||
|
|
7a752537c5 | ||
|
|
abd0b63fe9 | ||
|
|
7ac7cd452b | ||
|
|
94cecdb8c0 | ||
|
|
a8b35ba971 | ||
|
|
bc1ab84b75 | ||
|
|
acc895d2df | ||
|
|
23dbd0a92f | ||
|
|
c069b5cd97 | ||
|
|
f1dfe50d8b | ||
|
|
7d3820175f | ||
|
|
75032fdbb7 | ||
|
|
23b3ad63cf | ||
|
|
7afd0c86cd | ||
|
|
5f0a341d22 | ||
|
|
2a117376b7 | ||
|
|
2fc750532d | ||
|
|
477be63cff | ||
|
|
39b90357db | ||
|
|
2bd916eb73 | ||
|
|
4d1b450b33 | ||
|
|
cb70812cb7 | ||
|
|
42e030e368 | ||
|
|
f84cdc921d | ||
|
|
02c07e7f4f | ||
|
|
d6ea190688 | ||
|
|
b20487b928 | ||
|
|
004cf5693f | ||
|
|
ef8de6feb0 | ||
|
|
d791ebe634 | ||
|
|
5af4a1817e | ||
|
|
e97b8c55f3 | ||
|
|
99fcb76210 | ||
|
|
d4fd34165e | ||
|
|
bd4741a0a0 | ||
|
|
5945c32646 | ||
|
|
9a9dc2594d | ||
|
|
02547e9475 | ||
|
|
d81a823da1 | ||
|
|
df74bcc885 | ||
|
|
094bcebfa3 | ||
|
|
bb2a16720b | ||
|
|
86d9a0c0f3 | ||
|
|
5cf9d72ed3 | ||
|
|
801605676c | ||
|
|
a9dec75e59 | ||
|
|
08d9d90fe1 | ||
|
|
9749db9235 | ||
|
|
11073aaaa5 | ||
|
|
e35ddc4d53 | ||
|
|
99b06e813e | ||
|
|
7164349944 | ||
|
|
bf43b3adee | ||
|
|
6aa4b3c8a9 | ||
|
|
7a9f7ef95e | ||
|
|
ee1a9d3ec7 | ||
|
|
6a69e58be5 | ||
|
|
6e1d788677 | ||
|
|
24f8f789c5 | ||
|
|
2fb779f990 | ||
|
|
0a9d3cd309 | ||
|
|
977b1aba1c | ||
|
|
a02aeeb906 | ||
|
|
7e2e63137e | ||
|
|
4b35219c27 | ||
|
|
0247c4701d | ||
|
|
e2cd0b8e4f | ||
|
|
ee93171c63 | ||
|
|
ddd2302cb2 | ||
|
|
c96d2288b3 | ||
|
|
6f5a088091 | ||
|
|
9a07797e29 | ||
|
|
055a020d33 | ||
|
|
4681ff3827 | ||
|
|
cff92faf06 | ||
|
|
890daf5489 | ||
|
|
182bdeb766 | ||
|
|
a4a953b167 | ||
|
|
ff37b7e18c | ||
|
|
7aa0e78c15 | ||
|
|
d798807693 | ||
|
|
35b78c2da6 | ||
|
|
66a4291c97 | ||
|
|
e89a965aff | ||
|
|
85a9f10be4 | ||
|
|
8bc6e0ffec | ||
|
|
98fc0ade4a | ||
|
|
f5626c21f7 | ||
|
|
b5a76c7ff2 | ||
|
|
612bae4c93 | ||
|
|
e45bf338cb | ||
|
|
577261806c | ||
|
|
8842097bd4 | ||
|
|
a4b14dd0bd | ||
|
|
94f0142c7d | ||
|
|
319affa43f | ||
|
|
14f43a5976 | ||
|
|
9e9a9ac6de | ||
|
|
cc24cfc26f | ||
|
|
712f7dae4a | ||
|
|
6d20908465 | ||
|
|
e2f0fe71f4 | ||
|
|
1241b74562 | ||
|
|
2d610003d5 | ||
|
|
7b2952f4d6 | ||
|
|
b18c5e08bb | ||
|
|
63cb67f009 | ||
|
|
cd212cb978 | ||
|
|
d934c1fb4a | ||
|
|
c8327b4075 | ||
|
|
8410930d2d | ||
|
|
f5218a93f6 | ||
|
|
ca2f367950 | ||
|
|
c8559b757f | ||
|
|
db620ebe83 | ||
|
|
abc7a4fa42 | ||
|
|
b02d75eb3a | ||
|
|
2d225a6cdb | ||
|
|
ccca75ecbd | ||
|
|
2360808604 | ||
|
|
b7c2b0e8fa | ||
|
|
581ff2b840 | ||
|
|
8a75e202d6 | ||
|
|
c23d733cfd | ||
|
|
aab8b41da9 | ||
|
|
e9139ee56f | ||
|
|
24d3d88980 | ||
|
|
078acaa0e8 | ||
|
|
96cd7a2b63 | ||
|
|
bf095b2c76 | ||
|
|
a80e4df6f0 | ||
|
|
3cd0d91c22 | ||
|
|
716aaa272d | ||
|
|
87c13e4aec | ||
|
|
7005d66ed6 | ||
|
|
ba62ce24b8 | ||
|
|
2ec7131659 | ||
|
|
65f441f5b5 | ||
|
|
ee0e85a30f | ||
|
|
8cdb5d1857 | ||
|
|
5cb8620e82 | ||
|
|
f0733655f8 | ||
|
|
59a5a4a68a | ||
|
|
b9cd57d873 | ||
|
|
9504061e4e | ||
|
|
f41f3a3b63 | ||
|
|
8f0c80980c | ||
|
|
e9b46d38e3 | ||
|
|
3440af51b0 | ||
|
|
95db78bc0b | ||
|
|
bde10ad8ef | ||
|
|
b8c2766639 | ||
|
|
5bb8bf20fe | ||
|
|
db71b77da4 | ||
|
|
af19092a7d | ||
|
|
3020dc5c94 | ||
|
|
e28d5449b5 | ||
|
|
efb075c7ba | ||
|
|
31073d398e | ||
|
|
8bafb88bc4 | ||
|
|
dc82b94c6b | ||
|
|
cf6043b0de | ||
|
|
ce1095d6de | ||
|
|
9f08170cd3 | ||
|
|
5c66f5f5d2 | ||
|
|
1a9c3244a3 | ||
|
|
00688bbf33 | ||
|
|
787cc56ed4 | ||
|
|
3984083e23 | ||
|
|
a91790b16d | ||
|
|
b2549e8d48 | ||
|
|
768e5dd6c0 | ||
|
|
7899f1ec00 | ||
|
|
ef93946fb1 | ||
|
|
675061fd63 | ||
|
|
4b212eee0d | ||
|
|
0f81d5e503 | ||
|
|
6616f4f860 | ||
|
|
357a8745de | ||
|
|
cde72c04df | ||
|
|
a8767b0e15 | ||
|
|
785762ceb9 | ||
|
|
7383aa0973 | ||
|
|
7d992082fa | ||
|
|
1d41eadd0b | ||
|
|
7b247384ec | ||
|
|
f270885a4d | ||
|
|
5fa000f7e6 | ||
|
|
ae56bb1edd | ||
|
|
644ec6891d | ||
|
|
db729915ad | ||
|
|
69fcc7d12e | ||
|
|
e90852a401 | ||
|
|
71b9940916 | ||
|
|
175272744d | ||
|
|
635e107d66 | ||
|
|
44dff9a3a9 | ||
|
|
22298fbb97 | ||
|
|
7221bb36b3 | ||
|
|
5b3a5908a3 | ||
|
|
9bbb930323 | ||
|
|
54ce7dbde8 | ||
|
|
92fb704ad1 | ||
|
|
b9b79c2a65 | ||
|
|
b4178a0f78 | ||
|
|
92b2986889 | ||
|
|
2d1a7faafb | ||
|
|
a9ad777757 | ||
|
|
60cb8815a0 | ||
|
|
b7d1085500 | ||
|
|
0449464b3f | ||
|
|
7b00570f55 | ||
|
|
919a5f9c60 | ||
|
|
c957e14733 | ||
|
|
c68e15ca3d | ||
|
|
4578148ab9 | ||
|
|
0503ee9a96 | ||
|
|
1baafc08cc | ||
|
|
39daac6de4 | ||
|
|
03d2b1777b | ||
|
|
22aeb7aaf3 | ||
|
|
f985e24109 | ||
|
|
a99898729f | ||
|
|
3f7b57740d | ||
|
|
b17c09aa1e | ||
|
|
9e2849f967 | ||
|
|
e665ef0d4b | ||
|
|
f514e54d65 | ||
|
|
30b088a13f | ||
|
|
4ce5a461f4 | ||
|
|
aff38118e5 | ||
|
|
31d096e808 | ||
|
|
62d9c10baf | ||
|
|
09242575d8 | ||
|
|
763652cb01 | ||
|
|
4a2f606f3a | ||
|
|
9ce4ce77d0 | ||
|
|
b1d88ac2d4 | ||
|
|
3345b3a570 | ||
|
|
a48a87f17b | ||
|
|
a702691460 | ||
|
|
d5491cb7ef | ||
|
|
67e48b4003 | ||
|
|
cc3bafa07c | ||
|
|
ce67266f5f | ||
|
|
a9b51bb6c3 | ||
|
|
cb48a912e9 | ||
|
|
01d4afe9ef | ||
|
|
3a49ba7e0e | ||
|
|
b04675d62f | ||
|
|
6bd396a70b | ||
|
|
455d0006d7 | ||
|
|
ebb5f5d229 | ||
|
|
73ad912cd1 | ||
|
|
b559cf0500 | ||
|
|
ae3f69a5be | ||
|
|
8a2e470f0a | ||
|
|
6d9134aa25 | ||
|
|
ec7adea194 | ||
|
|
a77b7fda7b | ||
|
|
55d78f678d |
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
proxy
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
.*
|
||||||
|
release-*
|
||||||
|
proxy.crt
|
||||||
|
proxy.key
|
||||||
103
CHANGELOG
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
proxy更新日志
|
||||||
|
v4.2
|
||||||
|
1.优化了内网穿透,避免了client意外下线,导致链接信息残留的问题.
|
||||||
|
2.http代理增加了SNI支持,现在http(s)代理模式支持反向代理,支持http(s)透明代理.
|
||||||
|
3.增加了英文手册.
|
||||||
|
|
||||||
|
v4.1
|
||||||
|
1.优化了http(s),socks5代理中的域名智能判断,如果是内网IP,直接走本地网络,提升浏览体验,
|
||||||
|
同时优化了检查机制,判断更快.
|
||||||
|
2.http代理basic认证增加了对https协议的支持,现在basic认证可以控制所有http(s)流量了.
|
||||||
|
3.项目代码增加了依赖类库vendor目录,clone下来就能go build,再也不用担心go get依赖类库
|
||||||
|
失败导致不能编译了.
|
||||||
|
|
||||||
|
v4.0
|
||||||
|
1.内网穿透三端重构了一个multiplexing版本,使用github.com/xtaci/smux实现了tcp链接的多路复用,
|
||||||
|
鼎鼎大名的kcp-go底层就是使用的这个库,基于kcp-go的双边加速工具kcptun的广泛使用已经很好
|
||||||
|
的验证来该库的强大与稳定。multiplexing版的内网穿透对应的子命令分别是server,client,bridge
|
||||||
|
使用方式和参数与之前的子命令tserver,tclient,tserver完全一样,另外server,client增加了
|
||||||
|
压缩传输参数--c,使用压缩传输速度更快。
|
||||||
|
|
||||||
|
v3.9
|
||||||
|
1.增加了守护运行参数--forever,比如: proxy http --forever ,
|
||||||
|
proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后重启子进程.
|
||||||
|
该参数配合后台运行参数--daemon和日志参数--log,可以保障proxy一直在后台执行不会因为意外退出,
|
||||||
|
而且可以通过日志文件看到proxy的输出日志内容.
|
||||||
|
比如: proxy http -p ":9090" --forever --log proxy.log --daemon
|
||||||
|
|
||||||
|
v3.8
|
||||||
|
1.增加了日志输出到文件--log参数,比如: --log proxy.log,日志就会输出到proxy.log方便排除问题.
|
||||||
|
|
||||||
|
v3.7
|
||||||
|
1.修复了socks代理不能正常和上级代理通讯的问题.
|
||||||
|
|
||||||
|
|
||||||
|
v3.6
|
||||||
|
1.http(s),socks代理,集成了外部HTTP API认证,可以通过外部API对用户名和密码进行认证.
|
||||||
|
2.手册http(s),socks代理认证部分增加了集成外部HTTP API认证的使用说明.
|
||||||
|
|
||||||
|
v3.5
|
||||||
|
1.优化了kcp参数,速度有所提升.
|
||||||
|
2.修复了socks无法正常工作的问题.
|
||||||
|
3.修正了文档中的一些描述.
|
||||||
|
4.tcp代理增加了kcp协议传输数据.
|
||||||
|
5.优化了死循环检查,增加了添加本地IP参数,当VPS在nat设备后面,
|
||||||
|
vps上网卡IP都是内网IP,这个时候可以通过-g参数添加vps的外网ip防止死循环.
|
||||||
|
6.增加了--daemon参数,可以后台运行程序哟.
|
||||||
|
|
||||||
|
v3.4
|
||||||
|
1.socks5代理新增了用户名密码验证支持.
|
||||||
|
2.socks5,http(s)代理增加了kcp传输协议支持.
|
||||||
|
3.优化了内网穿透的心跳机制.
|
||||||
|
|
||||||
|
v3.3
|
||||||
|
1.修复了socks代理模式对证书文件的判断逻辑.
|
||||||
|
2.增强了http代理,socks代理的ssh中转模式的稳定性.
|
||||||
|
3.socks代理tls,tcp模式新增了CMD_ASSOCIATE(udp)支持.socks代理ssh模式不支持udp.
|
||||||
|
4.修复了http代理某些情况下会崩溃的bug.
|
||||||
|
|
||||||
|
v3.2
|
||||||
|
1.内网穿透功能server端-r参数增加了协议和key设置.
|
||||||
|
2.手册增加了对-r参数的详细说明.
|
||||||
|
3.修复了普通模式也检查证书文件的bug.
|
||||||
|
4.增加了Socks5支持,目前只支持TCP协议,不支持UDP协议.
|
||||||
|
5.Socks5上级代理支持ssh中转,linux服务器不需要任何服务端,本地一个proxy即可开心上网.
|
||||||
|
6.http(s)代理增加了ssh中转支持,linux服务器不需要任何服务端,本地一个proxy即可开心上网.
|
||||||
|
|
||||||
|
v3.1
|
||||||
|
1.优化了内网穿透功能,bridge,client和server只需要启动一个即可。
|
||||||
|
server端启动的时候可以指定client端要暴露的一个或者多个端口。
|
||||||
|
2.修复了重复解析命令行参数的问题。
|
||||||
|
3.手册增加了微信接口本地开发的示例。
|
||||||
|
4.增加了配置文件使用说明.
|
||||||
|
|
||||||
|
v3.0
|
||||||
|
1.此次更新不兼容2.x版本,重构了全部代码,架构更合理,利于功能模块的增加与维护。
|
||||||
|
2.增加了代理死循环检查,增强了安全性。
|
||||||
|
3.增加了反向代理模式(即:内网穿透),支持TCP和UDP两种协议,可以把任何局域网的机器A所在网络的任何端。
|
||||||
|
暴露到任何局域网的机器B的本地端口或暴露到任何公网VPS上。
|
||||||
|
4.正向代理增加了UDP模式支持。
|
||||||
|
|
||||||
|
|
||||||
|
v2.2
|
||||||
|
1.增加了强制使用上级代理参数always.可以使所有流量都走上级代理。
|
||||||
|
2.增加了定时检查网络是否正常,可以在本地网络不稳定的时候修复连接池状态,提升代理访问体验。
|
||||||
|
3.http代理增加了对ipv6地址的支持。
|
||||||
|
|
||||||
|
v2.1
|
||||||
|
1.增加了http basic验证功能,可以对http代理协议设置basic验证,用户名和密码支持来自文件或者命令行。
|
||||||
|
2.优化了域名检查方法,避免空连接的出现。
|
||||||
|
3.修复了连接上级代理超时参数传递错误导致超时过大的问题。
|
||||||
|
4.增加了连接池状态监测,如果上级代理或者网络出现问题,会及时重新初始化连接池,防止大量无效连接,降低浏览体验。
|
||||||
|
5.增加了对系统kill信号的捕获,可以在收到系统kill信号之后执行清理释放连接的操作.避免出现大量CLOSE_WAIT。
|
||||||
|
|
||||||
|
v2.0
|
||||||
|
1.增加了连接池功能,大幅提高了通过上级代理访问的速度。
|
||||||
|
2.HTTP代理模式,优化了请求URL的获取逻辑,可以支持:http,https,websocke。
|
||||||
|
3.增加了TCP代理模式,支持是否加密通讯。
|
||||||
|
4.优化了链接关闭逻辑,避免出现大量CLOSE_WAIT。
|
||||||
|
5.增加了黑白名单机制,更自由快速的访问。
|
||||||
|
6.优化了网站Block机制检测,判断更准确。
|
||||||
|
|
||||||
|
v1.0
|
||||||
|
1.始发版本,可以代理http,https。
|
||||||
133
Godeps/Godeps.json
generated
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "proxy",
|
||||||
|
"GoVersion": "go1.8",
|
||||||
|
"GodepVersion": "v79",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"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/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": "f899cbd3df85058aa20d1cf129473b18f2a2b49f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/cast5",
|
||||||
|
"Rev": "86e16787bfd59cb4db9e278c51a95488c141a5d6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/curve25519",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ed25519",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ed25519/internal/edwards25519",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/salsa20",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ssh",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/tea",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/twofish",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/xtea",
|
||||||
|
"Rev": "1843fabd21d7180cf65e36759986d00c13dbb0fd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/bpf",
|
||||||
|
"Rev": "114479435b31b5077a087cc5303a45cb5d355dc4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/internal/iana",
|
||||||
|
"Rev": "114479435b31b5077a087cc5303a45cb5d355dc4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/internal/socket",
|
||||||
|
"Rev": "114479435b31b5077a087cc5303a45cb5d355dc4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/ipv4",
|
||||||
|
"Rev": "114479435b31b5077a087cc5303a45cb5d355dc4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/time/rate",
|
||||||
|
"Rev": "8be79e1e0910c292df4e79c241bb7e8f7e725959"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/alecthomas/kingpin.v2",
|
||||||
|
"Comment": "v2.2.5",
|
||||||
|
"Rev": "1087e65c9441605df944fb12c33f0fe7072d18ca"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5
Godeps/Readme
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
||||||
687
LICENSE
@ -1,21 +1,674 @@
|
|||||||
MIT License
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (c) 2017 snail007
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Preamble
|
||||||
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
|
The GNU General Public License is a free, copyleft license for
|
||||||
copies or substantial portions of the Software.
|
software and other kinds of works.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
The licenses for most software and other practical works are designed
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
share and change all versions of a program--to make sure it remains free
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
GNU General Public License for most of our software; it applies also to
|
||||||
SOFTWARE.
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
{project} Copyright (C) {year} {fullname}
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|||||||
652
README.md
@ -1,66 +1,602 @@
|
|||||||
# goproxy
|
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/logo.jpg?raw=true" width="200"/>
|
||||||
[](https://github.com/snail007/goproxy/)
|
Proxy is a high performance HTTP, HTTPS, HTTPS, websocket, TCP, UDP, Socks5 proxy server implemented by golang. It supports parent proxy,nat forward,TCP/UDP port forwarding, SSH transfer. you can expose a local server behind a NAT or firewall to the internet.
|
||||||
[]()
|
|
||||||
[](https://github.com/snail007/goproxy/releases)
|
---
|
||||||
[](https://github.com/snail007/goproxy/releases)
|
|
||||||
|
[](https://github.com/snail007/goproxy/) []() [](https://github.com/snail007/goproxy/releases) [](https://github.com/snail007/goproxy/releases)
|
||||||
|
|
||||||
|
[中文手册](/README_ZH.md)
|
||||||
|
|
||||||
# 30秒简介
|
### Features
|
||||||
proxy是golang实现的高性能http,https,websocket,tcp代理服务器.程序本身可以作为一级代理,如果设置了上级代理那么可以作为二级代理,乃至N级代理.如果程序不是一级代理,而且上级代理也是本程序,那么可以加密和上级代理之间的通讯,采用底层tls高强度加密,安全无特征.代理时会自动判断访问的网站是否屏蔽,如果被屏蔽那么就会使用上级代理(前提是配置了上级代理)访问网站;如果访问的网站没有被屏蔽,为了加速访问,代理会直接访问网站,不使用上级代理.
|
- chain-style proxy: the program itself can be a primary proxy, and if a parent proxy is set, it can be used as a second level proxy or even a N level proxy.
|
||||||
[图文教程](docs/faststart.md)
|
- Encrypted communication: if the program is not a primary proxy, and the parent proxy is also the program, then it can communicate with the parent proxy by encryption. The TLS encryption is high-intensity encryption, and it is safe and featureless.
|
||||||
# 快速使用:
|
- Intelligent HTTP, SOCKS5 proxy: the program will automatically determine whether the site which it access is blocked, if the site is blocked, the program will use parent proxy (the premise is you set up a parent proxy) to access the site. If the site isn't blocked, in order to speed up the access, the program will directly access the site and don't use parent proxy.
|
||||||
提示:所有操作需要root权限.
|
- The black-and-white list of domain: It is very flexible to control the way which you visite site.
|
||||||
|
- Cross platform: no mater what the os (such as Linux, windows, and even Raspberry Pi) you use, you always can use proxy well.
|
||||||
|
- Multi protocol support: the program support HTTP (S), TCP, UDP, Websocket, SOCKS5 proxy.
|
||||||
|
- The TCP/UDP port forwarding is supported.
|
||||||
|
- Nat forwarding in different network is supported: the program support TCP protocol and UDP protocol.
|
||||||
|
- SSH forwarding: HTTP (S), SOCKS5 proxy support SSH transfer, parent Linux server does not need any server, a local proxy can be happy to access the Internet.
|
||||||
|
- [KCP](https://github.com/xtaci/kcp-go) protocol is supported: HTTP (S), SOCKS5 proxy supports the KCP protocol which can transmit data, reduce latency, and improve the browsing experience.
|
||||||
|
- The integrated external API, HTTP (S): SOCKS5 proxy authentication can be integrated with the external HTTP API, which can easily control the user's access through the external system.
|
||||||
|
|
||||||
|
### Why need these?
|
||||||
|
- Because for some reason, we cannot access our services elsewhere. We can build a secure tunnel to access our services through multiple connected proxy nodes.
|
||||||
|
- WeChat interface is developed locally, which is convenient to debug.
|
||||||
|
- Remote access to intranet machines.
|
||||||
|
- Play with partners in a LAN game.
|
||||||
|
- something used to be played only in the LAN, now it can be played anywhere.
|
||||||
|
- Instead of 剑内网通,显IP内网通,花生壳,frp and so on.
|
||||||
|
- ...
|
||||||
|
|
||||||
0.如果你的VPS是linux64位的系统,那么只需要执行下面一句,就可以完成自动安装和配置.
|
|
||||||
#curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.sh | bash
|
This page is the v4.0-v4.1 manual, and the other version of the manual can be checked by the following link.
|
||||||
安装完成,配置目录是/etc/proxy,更详细的使用方法参考下面的进一步了解.
|
- [v3.9 manual](https://github.com/snail007/goproxy/tree/v3.9)
|
||||||
|
- [v3.8 manual](https://github.com/snail007/goproxy/tree/v3.8)
|
||||||
|
- [v3.6-v3.7 manual](https://github.com/snail007/goproxy/tree/v3.6)
|
||||||
|
- [v3.5 manual](https://github.com/snail007/goproxy/tree/v3.5)
|
||||||
|
- [v3.4 manual](https://github.com/snail007/goproxy/tree/v3.4)
|
||||||
|
- [v3.3 manual](https://github.com/snail007/goproxy/tree/v3.3)
|
||||||
|
- [v3.2 manual](https://github.com/snail007/goproxy/tree/v3.2)
|
||||||
|
- [v3.1 manual](https://github.com/snail007/goproxy/tree/v3.1)
|
||||||
|
- [v3.0 manual](https://github.com/snail007/goproxy/tree/v3.0)
|
||||||
|
- [v2.x manual](https://github.com/snail007/goproxy/tree/v2.2)
|
||||||
|
|
||||||
如果你的vps不是linux64位系统,请按照下面的半自动步骤安装:
|
### How to find the organization?
|
||||||
1.登录你的VPS,下载守护进程monexec,选择合适你的版本,vps一般选择"linux_amd64.tar.gz"的即可.
|
[Click to join the communication organization](https://gitter.im/go-proxy/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)
|
||||||
下载地址:https://github.com/reddec/monexec/releases
|
|
||||||
比如下载到/root/proxy/
|
|
||||||
执行:
|
|
||||||
#mkdir /root/proxy/
|
|
||||||
#cd /root/proxy/
|
|
||||||
#wget https://github.com/reddec/monexec/releases/download/v0.1.1/monexec_0.1.1_linux_amd64.tar.gz
|
|
||||||
2.下载proxy
|
|
||||||
下载地址:https://github.com/snail007/goproxy/releases
|
|
||||||
#cd /root/proxy/
|
|
||||||
#wget https://github.com/snail007/goproxy/releases/download/v2.0/proxy-linux-amd64.tar.gz
|
|
||||||
3.下载自动安装脚本
|
|
||||||
#cd /root/proxy/
|
|
||||||
#wget https://raw.githubusercontent.com/snail007/goproxy/master/install.sh
|
|
||||||
#chmod +x install.sh
|
|
||||||
#./install.sh
|
|
||||||
|
|
||||||
# 进一步了解:
|
### Installation
|
||||||
1、作为普通一级代理。
|
- [Quick installation](#quick-installation)
|
||||||
默认监听0.0.0.0:33080端口,可以使用-p修改端口,-i修改绑定ip。
|
- [Manual installation](#manual-installation)
|
||||||
默认情况
|
|
||||||
./proxy
|
|
||||||
指定ip和端口
|
|
||||||
./proxy -i 192.168.1.100 -p 60080
|
|
||||||
|
|
||||||
2、作为普通二级代理。
|
### First use must be read
|
||||||
可以通过-P指定上级代理,格式是IP:端口
|
- [Environmental Science](#environmental-science)
|
||||||
./proxy -P "192.168.1.100:60080" -p 33080
|
- [Use configuration file](#use-configuration-file)
|
||||||
|
- [Debug output](#debug-output)
|
||||||
|
- [Using log files](#using-log-files)
|
||||||
|
- [Daemon mode](#daemon-mode)
|
||||||
|
- [Monitor mode](#monitor-mode)
|
||||||
|
- [Generating a communication certificate file](#generating-a-communication-certificate-file)
|
||||||
|
- [Safety advice](#safety-advice)
|
||||||
|
|
||||||
3、作为加密一级代理。
|
### Manual catalogues
|
||||||
加密模式的一级代理需要和加密的二级代理配合。
|
- [1.HTTP proxy](#1http-proxy)
|
||||||
加密模式需要证书和key文件,在linux上并安装了openssl命令,可以直接通过下面的命令生成证书和key文件。
|
- [1.1 Common HTTP proxy](#11common-http-proxy)
|
||||||
./proxy keygen
|
- [1.2 Common HTTP second level proxy](#12common-http-second-level-proxy)
|
||||||
会在当前目录下面生成一个证书文件proxy.crt和key文件proxy.key。
|
- [1.3 HTTP second level proxy(encrypted)](#13http-second-level-encrypted-proxy)
|
||||||
比如在你的vps上运行加密一级代理,使用参数-x即可,默认会使用程序相同目录下面的证书文件proxy.crt和key文件proxy.key。
|
- [1.4 HTTP third level proxy(encrypted)](#14http-third-level-encrypted-proxy)
|
||||||
./proxy -x
|
- [1.5 Basic Authentication](#15basic-authentication)
|
||||||
或者使用-c和-k指定证书和key文件,ip和端口。
|
- [1.6 HTTP proxy traffic force to go to parent http proxy](#16http-proxy-traffic-force-to-go-to-parent-http-proxy)
|
||||||
./proxy -x -c "proxy.crt" -k "proxy.key" -p 58080
|
- [1.7 Transfer through SSH](#17transfer-through-ssh)
|
||||||
|
- [1.7.1 The way of username and password](#171the-way-of-username-and-password)
|
||||||
|
- [1.7.2 The way of username and key](#172the-way-of-username-and-key)
|
||||||
|
- [1.8 KCP protocol transmission](#18kcp-protocol-transmission)
|
||||||
|
- [1.9 View help](#19view-help)
|
||||||
|
- [2.TCP proxy](#2tcp-proxy)
|
||||||
|
- [2.1 Common TCP first level proxy](#21common-tcp-first-level-proxy)
|
||||||
|
- [2.2 Common TCP second level proxy](#22common-tcp-second-level-proxy)
|
||||||
|
- [2.3 Common TCP third level proxy](#23common-tcp-third-level-proxy)
|
||||||
|
- [2.4 TCP second level encrypted proxy](#24tcp-second-level-encrypted-proxy)
|
||||||
|
- [2.5 TCP third level encrypted proxy](#25tcp-third-level-encrypted-proxy)
|
||||||
|
- [2.6 View help](#26view-help)
|
||||||
|
- [3.UDP proxy](#3udp-proxy)
|
||||||
|
- [3.1 Common UDP first level proxy](#31common-udp-first-level-proxy)
|
||||||
|
- [3.2 Common UDP second level proxy](#32common-udp-second-level-proxy)
|
||||||
|
- [3.3 Common UDP third level proxy](#33common-udp-third-level-proxy)
|
||||||
|
- [3.4 UDP second level encrypted proxy](#34udp-second-level-encrypted-proxy)
|
||||||
|
- [3.5 UDP third level encrypted proxy](#35udp-third-level-encrypted-proxy)
|
||||||
|
- [3.6 View help](#36view-help)
|
||||||
|
- [4.Nat forward](#4nat-forward)
|
||||||
|
- [4.1 Principle explanation](#41principle-explanation)
|
||||||
|
- [4.2 TCP common usage](#42tcp-common-usage)
|
||||||
|
- [4.3 Local development of WeChat interface](#43local-development-of-wechat-interface)
|
||||||
|
- [4.4 UDP common usage](#44udp-common-usage)
|
||||||
|
- [4.5 Advanced usage 1](#45advanced-usage-1)
|
||||||
|
- [4.6 Advanced usage 2](#46advanced-usage-2)
|
||||||
|
- [4.7 -r parameters of server](#47-r-parameters-of-server)
|
||||||
|
- [4.8 View help](#48view-help)
|
||||||
|
- [5.SOCKS5 proxy](#5socks5-proxy)
|
||||||
|
- [5.1 Common SOCKS5 proxy](#51common-socks5-proxy)
|
||||||
|
- [5.2 Common SOCKS5 second level proxy](#52common-socks5-second-level-proxy)
|
||||||
|
- [5.3 SOCKS5 second level proxy(encrypted)](#53socks-second-level-encrypted-proxy)
|
||||||
|
- [5.4 SOCKS third level proxy(encrypted)](#54socks-third-level-encrypted-proxy)
|
||||||
|
- [5.5 SOCKS proxy traffic force to go to parent socks proxy](#55socks-proxy-traffic-force-to-go-to-parent-socks-proxy)
|
||||||
|
- [5.6 Transfer through SSH](#56transfer-through-ssh)
|
||||||
|
- [5.6.1 The way of username and password](#561the-way-of-username-and-password)
|
||||||
|
- [5.6.2 The way of username and key](#562the-way-of-username-and-key)
|
||||||
|
- [5.7 Authentication](#57authentication)
|
||||||
|
- [5.8 KCP protocol transmission](#58kcp-protocol-transmission)
|
||||||
|
- [5.9 View help](#59view-help)
|
||||||
|
|
||||||
4、作为加密二级代理。
|
### Fast Start
|
||||||
加密模式的二级代理需要和加密的一级代理配合。加密模式的二级代理和加密模式的一级代理要使用相同的证书和key文件。
|
tips:all operations require root permissions.
|
||||||
默认会使用程序相同目录下面的证书文件proxy.crt和key文件proxy.key。
|
#### Quick installation
|
||||||
比如在你的windows电脑上允许二级加密代理,需要-P指定上级代理,同时设置-X代表是加密的上级代理。
|
#### **0. If your VPS is a linux64 system, you can complete the automatic installation and configuration by the following sentence.**
|
||||||
假设一级代理vps外网IP是:115.34.9.63。
|
```shell
|
||||||
./proxy.exe -X -P "115.34.9.63:58080" -c "proxy.crt" -k "proxy.key" -p 18080
|
curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.sh | bash
|
||||||
然后设置你的windos系统中,需要通过代理上网的程序的代理为http模式,地址为:127.0.0.1,端口为:18080,
|
```
|
||||||
然后程序即可通过加密通道通过vps上网。
|
The installation is completed, the configuration directory is /etc/proxy, more detailed use of the method referred to the following manual for further understanding.
|
||||||
|
If the installation fails or your VPS is not a linux64 system, please follow the semi-automatic step below:
|
||||||
|
|
||||||
|
#### Manual installation
|
||||||
|
|
||||||
任何使用问题欢迎邮件交流:arraykeys@gmail.com
|
#### **1.Download proxy**
|
||||||
|
Download address: https://github.com/snail007/goproxy/releases
|
||||||
|
```shell
|
||||||
|
cd /root/proxy/
|
||||||
|
wget https://github.com/snail007/goproxy/releases/download/v4.0/proxy-linux-amd64.tar.gz
|
||||||
|
```
|
||||||
|
#### **2.Download the automatic installation script**
|
||||||
|
```shell
|
||||||
|
cd /root/proxy/
|
||||||
|
wget https://raw.githubusercontent.com/snail007/goproxy/master/install.sh
|
||||||
|
chmod +x install.sh
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## **First use must be read**
|
||||||
|
|
||||||
|
### **Environmental Science**
|
||||||
|
The following tutorial, the default system is Linux, the program is proxy; all operations require root permissions.
|
||||||
|
If the system are windows, please use proxy.exe.
|
||||||
|
|
||||||
|
### **Use configuration file**
|
||||||
|
The following tutorial is to introduce the use method by the command line parameters, or by reading the configuration file to get the parameters.
|
||||||
|
The specific format is to specify a configuration file by the @ symbol, for example, ./proxy @configfile.txt.
|
||||||
|
configfile.txt's format: The first line is the subcommand name, and the second line begins one line: the long format of the parameter = the parameter value, there is no space and double quotes before and after.
|
||||||
|
The long format of the parameter's beginning is always --, the short format of the parameter's beginning is always -. If you don't know which short form parameter corresponds to the long format parameter, please look at the help command.
|
||||||
|
For example, the contents of configfile.txt are as follows:
|
||||||
|
```shell
|
||||||
|
http
|
||||||
|
--local-type=tcp
|
||||||
|
--local=:33080
|
||||||
|
```
|
||||||
|
### **Debug output**
|
||||||
|
By default, the log output information does not contain the number of file lines. In some cases, in order to eliminate and positione the program problem, You can use the --debug parameter to output the number of lines of code and the wrong time.
|
||||||
|
|
||||||
|
### **Using log files**
|
||||||
|
By default, the log is displayed directly on the console, and if you want to save it to the file, you can use the --log parameter.
|
||||||
|
for example, --log proxy.log, The log will be exported to proxy.log file which is easy to troubleshoot.
|
||||||
|
|
||||||
|
### **Generating a communication certificate file**
|
||||||
|
HTTP, TCP, UDP proxy process will communicate with parent proxy. In order to secure, we use encrypted communication. Of course, we can choose not to encrypted communication. All communication with parent proxy in this tutorial is encrypted, requiring certificate files.
|
||||||
|
The OpenSSL command is installed on the Linux and encrypted certificate can be generated directly through the following command.
|
||||||
|
`./proxy keygen`
|
||||||
|
By default, the certificate file proxy.crt and the key file proxy.key are generated under the current program directory.
|
||||||
|
|
||||||
|
### **Daemon mode**
|
||||||
|
After the default execution of proxy, if you want to keep proxy running, you can't close the command line.
|
||||||
|
If you want to run proxy in the daemon mode, the command line can be shut down, just add the --daemon parameter at the end of the command.
|
||||||
|
for example: `./proxy http -t tcp -p "0.0.0.0:38080" --daemon`
|
||||||
|
|
||||||
|
### **Monitor mode**
|
||||||
|
Monitor mode parameter --forever, for example: `proxy http --forever`,
|
||||||
|
Proxy will fork subprocess, then monitor the child process, if the subprocess exits, restarts the subprocess after 5 seconds.
|
||||||
|
This parameter, with the parameter --daemon and the log parameter --log, can guarantee that the proxy has been ran in the background and not exited accidentally.
|
||||||
|
And you can see the output log of proxy through the log file.
|
||||||
|
for example: `proxy http -p ":9090" --forever --log proxy.log --daemon`
|
||||||
|
|
||||||
|
### **Safety advice**
|
||||||
|
When vps is behind the NAT, the network card IP on VPS is an internal network IP, and then you can add the VPS's external network IP to prevent the dead cycle by -g parameter.
|
||||||
|
Assuming that your VPS outer external network IP is 23.23.23.23, the following command sets the 23.23.23.23 through the -g parameter.
|
||||||
|
`./proxy http -g "23.23.23.23"`
|
||||||
|
|
||||||
|
### **1.HTTP proxy**
|
||||||
|
#### **1.1.common HTTP proxy**
|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:38080"`
|
||||||
|
|
||||||
|
#### **1.2.Common HTTP second level proxy**
|
||||||
|
Using local port 8090, assume the parent HTTP proxy is: `22.22.22.22:8080`
|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" `
|
||||||
|
The connection pool is closed by default. If you want to speed up access speed, -L can open the connection pool, the 10 is the size of the connection pool, and the 0 is closed.
|
||||||
|
It is not good to stability of connection pool when the network is not good.
|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -L 10`
|
||||||
|
We can also specify the black and white list files of the domain name, one line for one domain name. The matching rule is the most right-hand matching, for example, baidu.com, which matches *.*.baidu.com. The domain name of the blacklist is directly headed by the parent proxy, and the domain name of the white list does not go to the parent proxy.
|
||||||
|
`./proxy http -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt`
|
||||||
|
|
||||||
|
#### **1.3.HTTP second level encrypted proxy**
|
||||||
|
HTTP first level proxy(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy http -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
HTTP second level proxy(local Linux)
|
||||||
|
`./proxy http -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
accessing the local 8080 port is accessing the proxy port 38080 above VPS.
|
||||||
|
|
||||||
|
HTTP second level proxy(local windows)
|
||||||
|
`./proxy.exe http -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
In your windos system, the mode of the program that needs to surf the Internet by proxy is setted up as HTTP mode, the address is 127.0.0.1, the port is: 8080, the program can go through the encrypted channel through VPS to surf on the internet.
|
||||||
|
|
||||||
|
#### **1.4.HTTP third level encrypted proxy**
|
||||||
|
HTTP first level proxy VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy http -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
HTTP second level proxy VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy http -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
HTTP third level proxy(local)
|
||||||
|
`./proxy http -t tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local 8080 port is access to the HTTP first level proxy which port is 38080.
|
||||||
|
|
||||||
|
#### **1.5.Basic Authentication**
|
||||||
|
We can do Basic authentication for the HTTP proxy, The authenticated username and password can be specified at the command line.
|
||||||
|
`./proxy http -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"`
|
||||||
|
If you need multiple users, repeat the -a parameters.
|
||||||
|
You can also be placed in a file, which is a line, a ‘username: password’, and then specified in -F.
|
||||||
|
`./proxy http -t tcp -p ":33080" -F auth-file.txt`
|
||||||
|
|
||||||
|
In addition, the HTTP (s) proxy also integrates external HTTP API authentication, and we can specify a HTTP URL interface address by the --auth-url parameter.
|
||||||
|
When somebody connect the proxy, which will request this URL by GET way, with the following four parameters, and if the HTTP state code 204 is returned, the authentication is successful.
|
||||||
|
In other cases, authentication failed.
|
||||||
|
for example:
|
||||||
|
`./proxy http -t tcp -p ":33080" --auth-url "http://test.com/auth.php"`
|
||||||
|
When the user connecte the proxy, which will request this URL by GET way("http://test.com/auth.php"),
|
||||||
|
with user, pass, IP, and target four parameters:
|
||||||
|
http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET}
|
||||||
|
user:username
|
||||||
|
pass:password
|
||||||
|
ip:user's IP,for example: 192.168.1.200
|
||||||
|
target:URL user connect to, for example: http://demo.com:80/1.html or https://www.baidu.com:80
|
||||||
|
|
||||||
|
If there is no -a or -F or --auth-url parameters, Basic authentication is closed.
|
||||||
|
|
||||||
|
#### **1.6.HTTP proxy traffic force to go to parent http proxy**
|
||||||
|
By default, proxy will intelligently judge whether a domain name can be accessed. If it cannot be accessed, it will access to parent HTTP proxy.
|
||||||
|
Through --always, all HTTP proxy traffic can be coercion to the parent HTTP proxy.
|
||||||
|
`./proxy http --always -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
#### **1.7.Transfer through SSH**
|
||||||
|
Explanation: the principle of SSH transfer is to take advantage of SSH's forwarding function, which is, after you connect to SSH, you can access to the target address through the SSH proxy.
|
||||||
|
Suppose there is a vps
|
||||||
|
- IP is 2.2.2.2, ssh port is 22, ssh username is user, ssh password is demo
|
||||||
|
- The SSH private key of the user is user.key
|
||||||
|
|
||||||
|
##### ***1.7.1.The way of username and password***
|
||||||
|
Local HTTP (S) proxy use 28080 port,excute:
|
||||||
|
`./proxy http -T ssh -P "2.2.2.2:22" -u user -A demo -t tcp -p ":28080"`
|
||||||
|
##### ***1.7.2.The way of username and key***
|
||||||
|
Local HTTP (S) proxy use 28080 port,excute:
|
||||||
|
`./proxy http -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"`
|
||||||
|
|
||||||
|
#### **1.8.KCP protocol transmission**
|
||||||
|
The KCP protocol requires a -B parameter to set a password which can encrypt and decrypt data.
|
||||||
|
|
||||||
|
Http first level proxy(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy http -t kcp -p ":38080" -B mypassword`
|
||||||
|
|
||||||
|
Http second level proxy(os is Linux)
|
||||||
|
`./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword`
|
||||||
|
Then access to the local 8080 port is access to the proxy's port 38080 on the VPS, and the data is transmitted through the KCP protocol.
|
||||||
|
|
||||||
|
#### **1.9.view help**
|
||||||
|
`./proxy help http`
|
||||||
|
|
||||||
|
### **2.TCP proxy**
|
||||||
|
|
||||||
|
#### **2.1.Common TCP first level proxy**
|
||||||
|
Local execution:
|
||||||
|
`./proxy tcp -p ":33080" -T tcp -P "192.168.22.33:22" -L 0`
|
||||||
|
Then access to the local 33080 port is the 22 port of access to 192.168.22.33.
|
||||||
|
|
||||||
|
#### **2.2.Common TCP second level proxy**
|
||||||
|
VPS(IP:22.22.22.33) execute:
|
||||||
|
`./proxy tcp -p ":33080" -T tcp -P "127.0.0.1:8080" -L 0`
|
||||||
|
local execution:
|
||||||
|
`./proxy tcp -p ":23080" -T tcp -P "22.22.22.33:33080"`
|
||||||
|
Then access to the local 23080 port is the 8080 port of access to 22.22.22.33.
|
||||||
|
|
||||||
|
#### **2.3.Common TCP third level proxy**
|
||||||
|
TCP first level proxy VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp -p ":38080" -T tcp -P "66.66.66.66:8080" -L 0`
|
||||||
|
TCP second level proxy VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp -p ":28080" -T tcp -P "22.22.22.22:38080"`
|
||||||
|
TCP third level proxy (local)
|
||||||
|
`./proxy tcp -p ":8080" -T tcp -P "33.33.33.33:28080"`
|
||||||
|
Then access to the local 8080 port is to access the 8080 port of the 66.66.66.66 by encrypting the TCP tunnel.
|
||||||
|
|
||||||
|
#### **2.4.TCP second level encrypted proxy**
|
||||||
|
VPS(IP:22.22.22.33) execute:
|
||||||
|
`./proxy tcp --tls -p ":33080" -T tcp -P "127.0.0.1:8080" -L 0 -C proxy.crt -K proxy.key`
|
||||||
|
local execution:
|
||||||
|
`./proxy tcp -p ":23080" -T tls -P "22.22.22.33:33080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local 23080 port is to access the 8080 port of the 22.22.22.33 by encrypting the TCP tunnel.
|
||||||
|
|
||||||
|
#### **2.5.TCP third level encrypted proxy**
|
||||||
|
TCP first level proxy VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp --tls -p ":38080" -T tcp -P "66.66.66.66:8080" -C proxy.crt -K proxy.key`
|
||||||
|
TCP second level proxy VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp --tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
TCP third level proxy (local)
|
||||||
|
`./proxy tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local 8080 port is to access the 8080 port of the 66.66.66.66 by encrypting the TCP tunnel.
|
||||||
|
|
||||||
|
#### **2.6.view help**
|
||||||
|
`./proxy help tcp`
|
||||||
|
|
||||||
|
### **3.UDP proxy**
|
||||||
|
|
||||||
|
#### **3.1.Common UDP first level proxy**
|
||||||
|
local execution:
|
||||||
|
`./proxy udp -p ":5353" -T udp -P "8.8.8.8:53"`
|
||||||
|
Then access to the local UDP:5353 port is access to the UDP:53 port of the 8.8.8.8.
|
||||||
|
|
||||||
|
#### **3.2.Common UDP second level proxy**
|
||||||
|
VPS(IP:22.22.22.33) execute:
|
||||||
|
`./proxy tcp -p ":33080" -T udp -P "8.8.8.8:53"`
|
||||||
|
local execution:
|
||||||
|
`./proxy udp -p ":5353" -T tcp -P "22.22.22.33:33080"`
|
||||||
|
Then access to the local UDP:5353 port is access to the UDP:53 port of the 8.8.8.8 through the TCP tunnel.
|
||||||
|
|
||||||
|
#### **3.3.Common UDP third level proxy**
|
||||||
|
TCP first level proxy VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp -p ":38080" -T udp -P "8.8.8.8:53"`
|
||||||
|
TCP second level proxy VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp -p ":28080" -T tcp -P "22.22.22.22:38080"`
|
||||||
|
TCP third level proxy (local)
|
||||||
|
`./proxy udp -p ":5353" -T tcp -P "33.33.33.33:28080"`
|
||||||
|
Then access to the local 5353 port is access to the 53 port of the 8.8.8.8 through the TCP tunnel.
|
||||||
|
|
||||||
|
#### **3.4.UDP second level encrypted proxy**
|
||||||
|
VPS(IP:22.22.22.33) execute:
|
||||||
|
`./proxy tcp --tls -p ":33080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key`
|
||||||
|
local execution:
|
||||||
|
`./proxy udp -p ":5353" -T tls -P "22.22.22.33:33080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local UDP:5353 port is access to the UDP:53 port of the 8.8.8.8 by the encrypting TCP tunnel.
|
||||||
|
|
||||||
|
#### **3.5.UDP third level encrypted proxy**
|
||||||
|
TCP first level proxy VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp --tls -p ":38080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key`
|
||||||
|
TCP second level proxy VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp --tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
TCP third level proxy (local)
|
||||||
|
`./proxy udp -p ":5353" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local UDP:5353 port is access to the UDP:53 port of the 8.8.8.8 by the encrypting TCP tunnel.
|
||||||
|
|
||||||
|
#### **3.6.view help**
|
||||||
|
`./proxy help udp`
|
||||||
|
|
||||||
|
### **4.Nat forward**
|
||||||
|
#### **4.1、Principle explanation**
|
||||||
|
Nat forward, divided into two versions, "multi-link version" and "multiplexed version", generally like web services Which is not a long time to connect the service recommended "multi-link version", if you want to keep long Time connection, "multiplexed version" is recommended.
|
||||||
|
1. Multilink version, the corresponding subcommand is tserver,tclient,tbridge。
|
||||||
|
1. Multiplexed version, the corresponding subcommand is server,client,bridge。
|
||||||
|
1. the parameters and use of Multilink version and multiplexed is exactly the same.
|
||||||
|
1. **Multiplexed version of the server, client can open the compressed transmission, the parameter is --c.**
|
||||||
|
1. **Server, client or both are open compression, either do not open, can not only open one.**
|
||||||
|
|
||||||
|
The following tutorial uses "Multiplexing Versions" as an example to illustrate how to use it.
|
||||||
|
Nat forward consists of three parts: client-side, server-side, bridge-side; client and server take the initiative to connect the bridge to bridge.
|
||||||
|
When the user access the server side, the process is:
|
||||||
|
1. Server and bridge initiative to establish a link;
|
||||||
|
1. Then the bridge notifies the client to connect the bridge, and connects the intranet target port;
|
||||||
|
1. Then bind the client to the bridge and client to the internal network port connection;
|
||||||
|
1. Then the bridge of the client over the connection and server-side connection binding;
|
||||||
|
1. The entire channel is completed;
|
||||||
|
|
||||||
|
#### **4.2.TCP common usage**
|
||||||
|
Background:
|
||||||
|
- The company computer A provides the 80 port of the web service
|
||||||
|
- There is one VPS, which public IP is 22.22.22.22
|
||||||
|
|
||||||
|
Demand:
|
||||||
|
You can access the 80 port of the company's computer by access to VPS's 28080 port when you are at home.
|
||||||
|
|
||||||
|
Procedure:
|
||||||
|
1. Execute on VPS
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server -r ":28080@:80" -P "127.0.0.1:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. Execute on the company's computer A
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. complete
|
||||||
|
|
||||||
|
#### **4.3.Local development of WeChat interface**
|
||||||
|
Background:
|
||||||
|
- My own computer provides the 80 port of nginx service
|
||||||
|
- There is one VPS, which public IP is 22.22.22.22
|
||||||
|
|
||||||
|
Demand:
|
||||||
|
Fill out the Web callback interface configuration address of WeChat Development Account: http://22.22.22.22/calback.php
|
||||||
|
Then you can access the calback.php under the 80 port of the computer, and if you need to bind the domain name, you can use your own domain name.
|
||||||
|
for example: Wx-dev.xxx.com is resolved to 22.22.22.22, and then configure the domain name wx-dev.xxx.com into a specific directory in the nginx of your own computer.
|
||||||
|
|
||||||
|
|
||||||
|
Procedure:
|
||||||
|
1. Execute on VPS and ensure that the 80 port of VPS is not occupied by other programs.
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server -r ":80@:80" -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. Execute it on your own computer
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. compolete
|
||||||
|
|
||||||
|
#### **4.4.UDP common usage**
|
||||||
|
Background:
|
||||||
|
- The company computer A provides the DNS resolution, the UDP:53 port.
|
||||||
|
- There is one VPS, which public IP is 22.22.22.22.
|
||||||
|
|
||||||
|
Demand:
|
||||||
|
You can use the company computer A for domain name resolution services by setting up local DNS as 22.22.22.22 at home.
|
||||||
|
|
||||||
|
Procedure:
|
||||||
|
1. Execute on VPS
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server --udp -r ":53@:53" -P "127.0.0.1:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. Execute on the company's computer A
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. compolete
|
||||||
|
|
||||||
|
#### **4.5.Advanced usage 1**
|
||||||
|
Background:
|
||||||
|
- The company computer A provides the 80 port of the web service
|
||||||
|
- There is one VPS, which public IP is 22.22.22.22
|
||||||
|
|
||||||
|
Demand:
|
||||||
|
For security, it doesn't want to be able to access the company's computer A on VPS. At home, it can access the 80 port of the company's computer A through the encrypted tunnel by accessing the 28080 port of you own computer.
|
||||||
|
|
||||||
|
Procedure:
|
||||||
|
1. Execute on VPS
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. Execute on the company's computer A
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. Execute it on your own computer
|
||||||
|
`./proxy server -r ":28080@:80" -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. compolete
|
||||||
|
|
||||||
|
#### **4.6.Advanced usage 2**
|
||||||
|
Tips:
|
||||||
|
If there are multiple client connected to the same bridge at the same time, you need to specify different key, which can be set by --k parameter. --k must be a unique string on the same bridge.
|
||||||
|
When server is connected to bridge, if multiple client is connected to the same bridge at the same time, you need to use the --k parameter to select client.
|
||||||
|
Repeating -r parameters can expose multiple ports: -r format is "local IP: local port @clientHOST:client port".
|
||||||
|
|
||||||
|
Background:
|
||||||
|
- The company computer A provides the web service 80 port and the FTP service 21 port
|
||||||
|
- There is one VPS, which public IP is 22.22.22.22
|
||||||
|
|
||||||
|
Demand:
|
||||||
|
You can access the 80 port of the company's computer by access to VPS's 28080 port at home.
|
||||||
|
You can access the 21 port of the company's computer by access to VPS's 29090 port at home.
|
||||||
|
|
||||||
|
Procedure:
|
||||||
|
1. Execute on VPS
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server -r ":28080@:80" -r ":29090@:21" --k test -P "127.0.0.1:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. Execute on the company's computer A
|
||||||
|
`./proxy client --k test -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. complete
|
||||||
|
|
||||||
|
#### **4.7.-r parameters of server**
|
||||||
|
The full format of the -r is:`PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT`
|
||||||
|
|
||||||
|
4.7.1.PROTOCOL is tcp or udp.
|
||||||
|
for example: `-r "udp://:10053@:53" -r "tcp://:10800@:1080" -r ":8080@:80"`
|
||||||
|
If the --udp parameter is specified, PROTOCOL is UDP by default, then `-r ": 8080@: 80"` is UDP.
|
||||||
|
If the --udp parameter is not specified, PROTOCOL is TCP by default, then `-r ": 8080@: 80"` is TCP.
|
||||||
|
|
||||||
|
4.7.2.CLIENT_KEY by default is 'default'.
|
||||||
|
for example: -r "udp://:10053@[test1]:53" -r "tcp://:10800@[test2]:1080" -r ":8080@:80"
|
||||||
|
If the --k parameter is specified, such as --k test, then `-r ":8080@:80"` CLIENT_KEY is 'test'.
|
||||||
|
If the --k parameter is not specified,then `-r ":8080@:80"`CLIENT_KEY is 'default'.
|
||||||
|
|
||||||
|
4.7.3.LOCAL_IP is empty which means LOCAL_IP is `0.0.0.0`, CLIENT_LOCAL_HOST is empty which means LOCAL_IP is `127.0.0.1`.
|
||||||
|
|
||||||
|
#### **4.8.view help**
|
||||||
|
`./proxy help bridge`
|
||||||
|
`./proxy help server`
|
||||||
|
`./proxy help server`
|
||||||
|
|
||||||
|
### **5.SOCKS5 proxy**
|
||||||
|
Tips: SOCKS5 proxy, support CONNECT, UDP protocol and don't support BIND and support username password authentication.
|
||||||
|
#### **5.1.Common SOCKS5 proxy**
|
||||||
|
`./proxy socks -t tcp -p "0.0.0.0:38080"`
|
||||||
|
|
||||||
|
#### **5.2.Common SOCKS5 second level proxy**
|
||||||
|
Using local port 8090, assume that the parent SOCKS5 proxy is `22.22.22.22:8080`
|
||||||
|
`./proxy socks -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" `
|
||||||
|
We can also specify the black and white list files of the domain name, one line for one domain name. The matching rule is the most right-hand matching. For example, baidu.com is *.*.baidu.com, the domain name of the blacklist is directly accessed by the parent proxy, and the domain name of the white list does not access to the parent proxy.
|
||||||
|
`./proxy socks -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt`
|
||||||
|
|
||||||
|
#### **5.3.SOCKS second level encrypted proxy**
|
||||||
|
SOCKS5 first level proxy(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy socks -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
SOCKS5 second level proxy(local Linux)
|
||||||
|
`./proxy socks -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local 8080 port is access to the proxy port 38080 above VPS.
|
||||||
|
|
||||||
|
SOCKS5 second level proxy(local windows)
|
||||||
|
`./proxy.exe socks -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
Then set up your windows system, the proxy that needs to surf the Internet by proxy is Socks5 mode, the address is: 127.0.0.1, the port is: 8080. the program can surf the Internet through the encrypted channel which is running on VPS.
|
||||||
|
|
||||||
|
#### **5.4.SOCKS third level encrypted proxy**
|
||||||
|
SOCKS5 first level proxy VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy socks -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
SOCKS5 second level proxy VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy socks -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
SOCKS5 third level proxy(local)
|
||||||
|
`./proxy socks -t tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
Then access to the local 8080 port is access to the proxy port 38080 above the SOCKS first level proxy.
|
||||||
|
|
||||||
|
#### **5.5.SOCKS proxy traffic force to go to parent socks proxy**
|
||||||
|
By default, proxy will intelligently judge whether a domain name can be accessed. If it cannot be accessed, it will go to parent SOCKS proxy. Through --always parameter, all SOCKS proxy traffic can be coercion to the parent SOCKS proxy.
|
||||||
|
`./proxy socks --always -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
#### **5.6.Transfer through SSH**
|
||||||
|
Explanation: the principle of SSH transfer is to take advantage of SSH's forwarding function, which is, after you connect to SSH, you can access the target address by the SSH.
|
||||||
|
Suppose there is a vps
|
||||||
|
- IP is 2.2.2.2, SSH port is 22, SSH username is user, SSH password is Demo
|
||||||
|
- The SSH private key name of the user is user.key
|
||||||
|
|
||||||
|
##### ***5.6.1.The way of username and password***
|
||||||
|
Local SOCKS5 proxy 28080 port, execute:
|
||||||
|
`./proxy socks -T ssh -P "2.2.2.2:22" -u user -A demo -t tcp -p ":28080"`
|
||||||
|
##### ***5.6.2.The way of username and key***
|
||||||
|
Local SOCKS5 proxy 28080 port, execute:
|
||||||
|
`./proxy socks -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"`
|
||||||
|
|
||||||
|
Then access to the local 28080 port is to access the target address through VPS.
|
||||||
|
|
||||||
|
#### **5.7.Authentication**
|
||||||
|
For socks5 proxy protocol we can use username and password authentication, username and password authentication can be specified on the command line.
|
||||||
|
`./proxy socks -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"`
|
||||||
|
If you need multiple users, repeat the -a parameters.
|
||||||
|
You can also be placed in a file, which is a line, a ‘username: password’, and then specified in -F.
|
||||||
|
`./proxy socks -t tcp -p ":33080" -F auth-file.txt`
|
||||||
|
|
||||||
|
In addition, socks5 proxy also integrates external HTTP API authentication, we can specify a http url interface address through the --auth-url parameter,
|
||||||
|
Then when the user is connected, the proxy GET request this url, with the following four parameters, if the return HTTP status code 204, on behalf of the authentication is successful.
|
||||||
|
In other cases, the authentication fails.
|
||||||
|
for example:
|
||||||
|
`./proxy socks -t tcp -p ":33080" --auth-url "http://test.com/auth.php"`
|
||||||
|
When the user is connected, the proxy will request this URL ("http://test.com/auth.php") by GET way.
|
||||||
|
With user, pass, IP, three parameters:
|
||||||
|
http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}
|
||||||
|
user:username
|
||||||
|
pass:password
|
||||||
|
ip: user's IP, for example: 192.168.1.200
|
||||||
|
|
||||||
|
If there is no -a or -F or --auth-url parameters, it means to turn off the authentication.
|
||||||
|
|
||||||
|
#### **5.8.KCP protocol transmission**
|
||||||
|
The KCP protocol requires a -B parameter to set a password to encrypt and decrypt data.
|
||||||
|
|
||||||
|
HTTP first level proxy(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy socks -t kcp -p ":38080" -B mypassword`
|
||||||
|
|
||||||
|
HTTP two level proxy(local os is Linux)
|
||||||
|
`./proxy socks -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword`
|
||||||
|
Then access to the local 8080 port is access to the proxy port 38080 on the VPS, and the data is transmitted through the KCP protocol.
|
||||||
|
|
||||||
|
#### **5.9.view help**
|
||||||
|
`./proxy help socks`
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
- Welcome adding group feedback...
|
||||||
|
|
||||||
|
### How to use the source code?
|
||||||
|
use command cd to enter your go SRC directory and then git clone https://github.com/snail007/goproxy.git and execute ./proxy.
|
||||||
|
Direct compilation: go build
|
||||||
|
execution: go run *.go
|
||||||
|
Utils is a toolkit, and service is a specific service class.
|
||||||
|
|
||||||
|
### License
|
||||||
|
Proxy is licensed under GPLv3 license.
|
||||||
|
### Contact
|
||||||
|
proxy QQ group:189618940
|
||||||
|
|
||||||
|
### Donation
|
||||||
|
if proxy help you a lot,you can support us by:
|
||||||
|
### AliPay
|
||||||
|
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/alipay.jpg?raw=true" width="200"/>
|
||||||
|
|
||||||
|
### Wechat Pay
|
||||||
|
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/wxpay.jpg?raw=true" width="200"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
676
README_ZH.md
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/logo.jpg?raw=true" width="200"/>
|
||||||
|
Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务器,支持正向代理、反向代理、透明代理、内网穿透、TCP/UDP端口映射、SSH中转,TLS加密传输。下载地址:https://github.com/snail007/goproxy/releases 官方QQ交流群:189618940
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](https://github.com/snail007/goproxy/) []() [](https://github.com/snail007/goproxy/releases) [](https://github.com/snail007/goproxy/releases)
|
||||||
|
|
||||||
|
[English Manual](/README.md)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- 链式代理,程序本身可以作为一级代理,如果设置了上级代理那么可以作为二级代理,乃至N级代理.
|
||||||
|
- 通讯加密,如果程序不是一级代理,而且上级代理也是本程序,那么可以加密和上级代理之间的通讯,采用底层tls高强度加密,安全无特征.
|
||||||
|
- 智能HTTP,SOCKS5代理,会自动判断访问的网站是否屏蔽,如果被屏蔽那么就会使用上级代理(前提是配置了上级代理)访问网站;如果访问的网站没有被屏蔽,为了加速访问,代理会直接访问网站,不使用上级代理.
|
||||||
|
- 域名黑白名单,更加自由的控制网站的访问方式。
|
||||||
|
- 跨平台性,无论你是widows,linux,还是mac,甚至是树莓派,都可以很好的运行proxy.
|
||||||
|
- 多协议支持,支持HTTP(S),TCP,UDP,Websocket,SOCKS5代理.
|
||||||
|
- TCP/UDP端口转发.
|
||||||
|
- 支持内网穿透,协议支持TCP和UDP.
|
||||||
|
- SSH中转,HTTP(S),SOCKS5代理支持SSH中转,上级Linux服务器不需要任何服务端,本地一个proxy即可开心上网.
|
||||||
|
- [KCP](https://github.com/xtaci/kcp-go)协议支持,HTTP(S),SOCKS5代理支持KCP协议传输数据,降低延迟,提升浏览体验.
|
||||||
|
- 集成外部API,HTTP(S),SOCKS5代理认证功能可以与外部HTTP API集成,可以方便的通过外部系统控制代理用户.
|
||||||
|
- 反向代理,支持直接把域名解析到proxy监听的ip,然后proxy就会帮你代理访问需要访问的HTTP(S)网站.
|
||||||
|
- 透明HTTP(S)代理,配合iptables,在网关直接把出去的80,443方向的流量转发到proxy,就能实现无感知的智能路由器代理.
|
||||||
|
|
||||||
|
### Why need these?
|
||||||
|
- 当由于某某原因,我们不能访问我们在其它地方的服务,我们可以通过多个相连的proxy节点建立起一个安全的隧道访问我们的服务.
|
||||||
|
- 微信接口本地开发,方便调试.
|
||||||
|
- 远程访问内网机器.
|
||||||
|
- 和小伙伴一起玩局域网游戏.
|
||||||
|
- 以前只能在局域网玩的,现在可以在任何地方玩.
|
||||||
|
- 替代圣剑内网通,显IP内网通,花生壳之类的工具.
|
||||||
|
- ...
|
||||||
|
|
||||||
|
|
||||||
|
本页是v4.2手册,其他版本手册请点击下面链接查看.
|
||||||
|
- [v4.0-v4.1手册](https://github.com/snail007/goproxy/tree/v4.1)
|
||||||
|
- [v3.9手册](https://github.com/snail007/goproxy/tree/v3.9)
|
||||||
|
- [v3.8手册](https://github.com/snail007/goproxy/tree/v3.8)
|
||||||
|
- [v3.6-v3.7手册](https://github.com/snail007/goproxy/tree/v3.6)
|
||||||
|
- [v3.5手册](https://github.com/snail007/goproxy/tree/v3.5)
|
||||||
|
- [v3.4手册](https://github.com/snail007/goproxy/tree/v3.4)
|
||||||
|
- [v3.3手册](https://github.com/snail007/goproxy/tree/v3.3)
|
||||||
|
- [v3.2手册](https://github.com/snail007/goproxy/tree/v3.2)
|
||||||
|
- [v3.1手册](https://github.com/snail007/goproxy/tree/v3.1)
|
||||||
|
- [v3.0手册](https://github.com/snail007/goproxy/tree/v3.0)
|
||||||
|
- [v2.x手册](https://github.com/snail007/goproxy/tree/v2.2)
|
||||||
|
|
||||||
|
### 怎么找到组织?
|
||||||
|
[点击加入交流组织](https://gitter.im/go-proxy/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
1. [快速安装](#自动安装)
|
||||||
|
1. [手动安装](#手动安装)
|
||||||
|
|
||||||
|
### 首次使用必看
|
||||||
|
- [环境](#首次使用必看-1)
|
||||||
|
- [使用配置文件](#使用配置文件)
|
||||||
|
- [调试输出](#调试输出)
|
||||||
|
- [使用日志文件](#使用日志文件)
|
||||||
|
- [后台运行](#后台运行)
|
||||||
|
- [守护运行](#守护运行)
|
||||||
|
- [生成通讯证书文件](#生成加密通讯需要的证书文件)
|
||||||
|
- [安全建议](#安全建议)
|
||||||
|
|
||||||
|
### 手册目录
|
||||||
|
- [1. HTTP代理](#1http代理)
|
||||||
|
- [1.1 普通HTTP代理](#11普通http代理)
|
||||||
|
- [1.2 普通二级HTTP代理](#12普通二级http代理)
|
||||||
|
- [1.3 HTTP二级代理(加密)](#13http二级代理加密)
|
||||||
|
- [1.4 HTTP三级代理(加密)](#14http三级代理加密)
|
||||||
|
- [1.5 Basic认证](#15basic认证)
|
||||||
|
- [1.6 强制走上级HTTP代理](#16http代理流量强制走上级http代理)
|
||||||
|
- [1.7 通过SSH中转](#17https通过ssh中转)
|
||||||
|
- [1.7.1 用户名和密码的方式](#171-ssh用户名和密码的方式)
|
||||||
|
- [1.7.2 用户名和密钥的方式](#172-ssh用户名和密钥的方式)
|
||||||
|
- [1.8 KCP协议传输](#18kcp协议传输)
|
||||||
|
- [1.9 HTTP(S)反向代理](#19-https反向代理)
|
||||||
|
- [1.10 HTTP(S)透明代理](#110-https透明代理)
|
||||||
|
- [1.11 查看帮助](#111查看帮助)
|
||||||
|
- [2. TCP代理](#2tcp代理)
|
||||||
|
- [2.1 普通一级TCP代理](#21普通一级tcp代理)
|
||||||
|
- [2.2 普通二级TCP代理](#22普通二级tcp代理)
|
||||||
|
- [2.3 普通三级TCP代理](#23普通三级tcp代理)
|
||||||
|
- [2.4 加密二级TCP代理](#24加密二级tcp代理)
|
||||||
|
- [2.5 加密三级TCP代理](#25加密三级tcp代理)
|
||||||
|
- [2.6 查看帮助](#26查看帮助)
|
||||||
|
- [3. UDP代理](#3udp代理)
|
||||||
|
- [3.1 普通一级UDP代理](#31普通一级udp代理)
|
||||||
|
- [3.2 普通二级UDP代理](#32普通二级udp代理)
|
||||||
|
- [3.3 普通三级UDP代理](#33普通三级udp代理)
|
||||||
|
- [3.4 加密二级UDP代理](#34加密二级udp代理)
|
||||||
|
- [3.5 加密三级UDP代理](#35加密三级udp代理)
|
||||||
|
- [3.6 查看帮助](#36查看帮助)
|
||||||
|
- [4. 内网穿透](#4内网穿透)
|
||||||
|
- [4.1 原理说明](#41原理说明)
|
||||||
|
- [4.2 TCP普通用法](#42tcp普通用法)
|
||||||
|
- [4.3 微信接口本地开发](#43微信接口本地开发)
|
||||||
|
- [4.4 UDP普通用法](#44udp普通用法)
|
||||||
|
- [4.5 高级用法一](#45高级用法一)
|
||||||
|
- [4.6 高级用法一](#46高级用法二)
|
||||||
|
- [4.7 server的-r参数](#47server的-r参数)
|
||||||
|
- [4.8 查看帮助](#48查看帮助)
|
||||||
|
- [5. SOCKS5代理](#5socks5代理)
|
||||||
|
- [5.1 普通SOCKS5代理](#51普通socks5代理)
|
||||||
|
- [5.2 普通二级SOCKS5代理](#52普通二级socks5代理)
|
||||||
|
- [5.3 SOCKS二级代理(加密)](#53socks二级代理加密)
|
||||||
|
- [5.4 SOCKS三级代理(加密)](#54socks三级代理加密)
|
||||||
|
- [5.5 流量强制走上级SOCKS代理](#55socks代理流量强制走上级socks代理)
|
||||||
|
- [5.6 通过SSH中转](#56socks通过ssh中转)
|
||||||
|
- [5.6.1 用户名和密码的方式](#561-ssh用户名和密码的方式)
|
||||||
|
- [5.6.2 用户名和密钥的方式](#562-ssh用户名和密钥的方式)
|
||||||
|
- [5.7 认证](#57认证)
|
||||||
|
- [5.8 KCP协议传输](#58kcp协议传输)
|
||||||
|
- [5.9 查看帮助](#59查看帮助)
|
||||||
|
|
||||||
|
### Fast Start
|
||||||
|
提示:所有操作需要root权限.
|
||||||
|
#### 自动安装
|
||||||
|
#### **0.如果你的VPS是linux64位的系统,那么只需要执行下面一句,就可以完成自动安装和配置.**
|
||||||
|
```shell
|
||||||
|
curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.sh | bash
|
||||||
|
```
|
||||||
|
安装完成,配置目录是/etc/proxy,更详细的使用方法参考下面的进一步了解.
|
||||||
|
如果安装失败或者你的vps不是linux64位系统,请按照下面的半自动步骤安装:
|
||||||
|
|
||||||
|
#### 手动安装
|
||||||
|
|
||||||
|
#### **1.下载proxy**
|
||||||
|
下载地址:https://github.com/snail007/goproxy/releases
|
||||||
|
```shell
|
||||||
|
cd /root/proxy/
|
||||||
|
wget https://github.com/snail007/goproxy/releases/download/v4.2/proxy-linux-amd64.tar.gz
|
||||||
|
```
|
||||||
|
#### **2.下载自动安装脚本**
|
||||||
|
```shell
|
||||||
|
cd /root/proxy/
|
||||||
|
wget https://raw.githubusercontent.com/snail007/goproxy/master/install.sh
|
||||||
|
chmod +x install.sh
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## **首次使用必看**
|
||||||
|
|
||||||
|
### **环境**
|
||||||
|
接下来的教程,默认系统是linux,程序是proxy;所有操作需要root权限;
|
||||||
|
如果你的是windows,请使用windows版本的proxy.exe即可.
|
||||||
|
|
||||||
|
### **使用配置文件**
|
||||||
|
接下来的教程都是通过命令行参数介绍使用方法,也可以通过读取配置文件获取参数.
|
||||||
|
具体格式是通过@符号指定配置文件,例如:./proxy @configfile.txt
|
||||||
|
configfile.txt里面的格式是,第一行是子命令名称,第二行开始一行一个:参数的长格式=参数值,前后不能有空格和双引号.
|
||||||
|
参数的长格式都是--开头的,短格式参数都是-开头,如果你不知道某个短格式参数对应长格式参数,查看帮助命令即可.
|
||||||
|
比如configfile.txt内容如下:
|
||||||
|
```shell
|
||||||
|
http
|
||||||
|
--local-type=tcp
|
||||||
|
--local=:33080
|
||||||
|
```
|
||||||
|
### **调试输出**
|
||||||
|
默认情况下,日志输出的信息不包含文件行数,某些情况下为了排除程序问题,快速定位问题,
|
||||||
|
可以使用--debug参数,输出代码行数和毫秒时间.
|
||||||
|
|
||||||
|
### **使用日志文件**
|
||||||
|
默认情况下,日志是直接在控制台显示出来的,如果要保存到文件,可以使用--log参数,
|
||||||
|
比如: --log proxy.log,日志就会输出到proxy.log方便排除问题.
|
||||||
|
|
||||||
|
|
||||||
|
### **生成加密通讯需要的证书文件**
|
||||||
|
http,tcp,udp代理过程会和上级通讯,为了安全我们采用加密通讯,当然可以选择不加密通信通讯,本教程所有和上级通讯都采用加密,需要证书文件.
|
||||||
|
在linux上并安装了openssl命令,可以直接通过下面的命令生成证书和key文件.
|
||||||
|
`./proxy keygen`
|
||||||
|
默认会在当前程序目录下面生成证书文件proxy.crt和key文件proxy.key。
|
||||||
|
|
||||||
|
### **后台运行**
|
||||||
|
默认执行proxy之后,如果要保持proxy运行,不能关闭命令行.
|
||||||
|
如果想在后台运行proxy,命令行可以关闭,只需要在命令最后加上--daemon参数即可.
|
||||||
|
比如:
|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:38080" --daemon`
|
||||||
|
|
||||||
|
### **守护运行**
|
||||||
|
守护运行参数--forever,比如: `proxy http --forever` ,
|
||||||
|
proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后重启子进程.
|
||||||
|
该参数配合后台运行参数--daemon和日志参数--log,可以保障proxy一直在后台执行不会因为意外退出,
|
||||||
|
而且可以通过日志文件看到proxy的输出日志内容.
|
||||||
|
比如: `proxy http -p ":9090" --forever --log proxy.log --daemon`
|
||||||
|
|
||||||
|
### **安全建议**
|
||||||
|
当VPS在nat设备后面,vps上网卡IP都是内网IP,这个时候可以通过-g参数添加vps的外网ip防止死循环.
|
||||||
|
假设你的vps外网ip是23.23.23.23,下面命令通过-g参数设置23.23.23.23
|
||||||
|
`./proxy http -g "23.23.23.23"`
|
||||||
|
|
||||||
|
### **1.HTTP代理**
|
||||||
|
#### **1.1.普通HTTP代理**
|
||||||
|

|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:38080"`
|
||||||
|
|
||||||
|
#### **1.2.普通二级HTTP代理**
|
||||||
|
使用本地端口8090,假设上级HTTP代理是`22.22.22.22:8080`
|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" `
|
||||||
|
默认关闭了连接池,如果要加快访问速度,-L可以开启连接池,10就是连接池大小,0为关闭,
|
||||||
|
开启连接池在网络不好的情况下,稳定不是很好.
|
||||||
|
`./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -L 10`
|
||||||
|
我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理.
|
||||||
|
`./proxy http -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt`
|
||||||
|
|
||||||
|
#### **1.3.HTTP二级代理(加密)**
|
||||||
|
一级HTTP代理(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy http -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
二级HTTP代理(本地Linux)
|
||||||
|
`./proxy http -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地的8080端口就是访问VPS上面的代理端口38080.
|
||||||
|
|
||||||
|
二级HTTP代理(本地windows)
|
||||||
|
`./proxy.exe http -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
然后设置你的windos系统中,需要通过代理上网的程序的代理为http模式,地址为:127.0.0.1,端口为:8080,程序即可通过加密通道通过vps上网。
|
||||||
|
|
||||||
|
#### **1.4.HTTP三级代理(加密)**
|
||||||
|
一级HTTP代理VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy http -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
二级HTTP代理VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy http -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
三级HTTP代理(本地)
|
||||||
|
`./proxy http -t tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地的8080端口就是访问一级HTTP代理上面的代理端口38080.
|
||||||
|
|
||||||
|
#### **1.5.Basic认证**
|
||||||
|
对于代理HTTP协议我们可以basic进行Basic认证,认证的用户名和密码可以在命令行指定
|
||||||
|
`./proxy http -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"`
|
||||||
|
多个用户,重复-a参数即可.
|
||||||
|
也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定.
|
||||||
|
`./proxy http -t tcp -p ":33080" -F auth-file.txt`
|
||||||
|
|
||||||
|
另外,http(s)代理还集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址,
|
||||||
|
然后有用户连接的时候,proxy会GET方式请求这url,带上下面四个参数,如果返回HTTP状态码204,代表认证成功
|
||||||
|
其它情况认为认证失败.
|
||||||
|
比如:
|
||||||
|
`./proxy http -t tcp -p ":33080" --auth-url "http://test.com/auth.php"`
|
||||||
|
用户连接的时候,proxy会GET方式请求这url("http://test.com/auth.php"),
|
||||||
|
带上user,pass,ip,target四个参数:
|
||||||
|
http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET}
|
||||||
|
user:用户名
|
||||||
|
pass:密码
|
||||||
|
ip:用户的IP,比如:192.168.1.200
|
||||||
|
target:用户访问的URL,比如:http://demo.com:80/1.html或https://www.baidu.com:80
|
||||||
|
|
||||||
|
如果没有-a或-F或--auth-url参数,就是关闭Basic认证.
|
||||||
|
|
||||||
|
#### **1.6.HTTP代理流量强制走上级HTTP代理**
|
||||||
|
默认情况下,proxy会智能判断一个网站域名是否无法访问,如果无法访问才走上级HTTP代理.通过--always可以使全部HTTP代理流量强制走上级HTTP代理.
|
||||||
|
`./proxy http --always -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
#### **1.7.HTTP(S)通过SSH中转**
|
||||||
|
说明:ssh中转的原理是利用了ssh的转发功能,就是你连接上ssh之后,可以通过ssh代理访问目标地址.
|
||||||
|
假设有:vps
|
||||||
|
- IP是2.2.2.2, ssh端口是22, ssh用户名是:user, ssh用户密码是:demo
|
||||||
|
- 用户user的ssh私钥名称是user.key
|
||||||
|
|
||||||
|
##### ***1.7.1 ssh用户名和密码的方式***
|
||||||
|
本地HTTP(S)代理28080端口,执行:
|
||||||
|
`./proxy http -T ssh -P "2.2.2.2:22" -u user -A demo -t tcp -p ":28080"`
|
||||||
|
##### ***1.7.2 ssh用户名和密钥的方式***
|
||||||
|
本地HTTP(S)代理28080端口,执行:
|
||||||
|
`./proxy http -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"`
|
||||||
|
|
||||||
|
#### **1.8.KCP协议传输**
|
||||||
|
KCP协议需要-B参数设置一个密码用于加密解密数据
|
||||||
|
|
||||||
|
一级HTTP代理(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy http -t kcp -p ":38080" -B mypassword`
|
||||||
|
|
||||||
|
二级HTTP代理(本地Linux)
|
||||||
|
`./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword`
|
||||||
|
那么访问本地的8080端口就是访问VPS上面的代理端口38080,数据通过kcp协议传输.
|
||||||
|
|
||||||
|
#### **1.9 HTTP(S)反向代理**
|
||||||
|
proxy不仅支持在其他软件里面通过设置代理的方式,为其他软件提供代理服务,而且支持直接把请求的网站域名解析到proxy监听的ip上,然后proxy监听80和443端口,那么proxy就会自动为你代理访问需要访问的HTTP(S)网站.
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
在"最后一级proxy代理"的机器上,因为proxy要伪装成所有网站,网站默认的端口HTTP是80,HTTPS是443,让proxy监听80和443端口即可.参数-p多个地址用逗号分割.
|
||||||
|
`./proxy http -t tcp -p :80,:443`
|
||||||
|
|
||||||
|
这个命令就在机器上启动了一个proxy代理,同时监听80和443端口,既可以当作普通的代理使用,也可以直接把需要代理的域名解析到这个机器的IP上.
|
||||||
|
|
||||||
|
如果有上级代理那么参照上面教程设置上级即可,使用方式完全一样.
|
||||||
|
`./proxy http -t tcp -p :80,:443 -T tls -P "2.2.2.2:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
注意:
|
||||||
|
proxy所在的服务器的DNS解析结果不能受到自定义的解析影响,不然就死循环了.
|
||||||
|
|
||||||
|
#### **1.10 HTTP(S)透明代理**
|
||||||
|
该模式需要具有一定的网络基础,相关概念不懂的请自行搜索解决.
|
||||||
|
假设proxy现在在路由器上运行,启动命令如下:
|
||||||
|
`./proxy http -t tcp -p :33080 -T tls -P "2.2.2.2:33090" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
然后添加iptables规则,下面是参考规则:
|
||||||
|
```shell
|
||||||
|
#上级proxy服务端服务器IP地址:
|
||||||
|
proxy_server_ip=2.2.2.2
|
||||||
|
|
||||||
|
#路由器运行proxy监听的端口:
|
||||||
|
proxy_local_port=33080
|
||||||
|
|
||||||
|
#下面的就不用修改了
|
||||||
|
#create a new chain named PROXY
|
||||||
|
iptables -t nat -N PROXY
|
||||||
|
|
||||||
|
# Ignore your PROXY server's addresses
|
||||||
|
# It's very IMPORTANT, just be careful.
|
||||||
|
|
||||||
|
iptables -t nat -A PROXY -d $proxy_server_ip -j RETURN
|
||||||
|
|
||||||
|
# Ignore LANs IP address
|
||||||
|
iptables -t nat -A PROXY -d 0.0.0.0/8 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 10.0.0.0/8 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 127.0.0.0/8 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 169.254.0.0/16 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 172.16.0.0/12 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 192.168.0.0/16 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 224.0.0.0/4 -j RETURN
|
||||||
|
iptables -t nat -A PROXY -d 240.0.0.0/4 -j RETURN
|
||||||
|
|
||||||
|
# Anything to port 80 443 should be redirected to PROXY's local port
|
||||||
|
iptables -t nat -A PROXY -p tcp --dport 80 -j REDIRECT --to-ports $proxy_local_port
|
||||||
|
iptables -t nat -A PROXY -p tcp --dport 443 -j REDIRECT --to-ports $proxy_local_port
|
||||||
|
|
||||||
|
# Apply the rules to nat client
|
||||||
|
iptables -t nat -A PREROUTING -p tcp -j PROXY
|
||||||
|
# Apply the rules to localhost
|
||||||
|
iptables -t nat -A OUTPUT -p tcp -j PROXY
|
||||||
|
```
|
||||||
|
- 清空整个链 iptables -F 链名比如iptables -t nat -F PROXY
|
||||||
|
- 删除指定的用户自定义链 iptables -X 链名 比如 iptables -t nat -X PROXY
|
||||||
|
- 从所选链中删除规则 iptables -D 链名 规则详情 比如 iptables -t nat -D PROXY -d 223.223.192.0/255.255.240.0 -j RETURN
|
||||||
|
|
||||||
|
#### **1.9.查看帮助**
|
||||||
|
`./proxy help http`
|
||||||
|
|
||||||
|
### **2.TCP代理**
|
||||||
|
|
||||||
|
#### **2.1.普通一级TCP代理**
|
||||||
|

|
||||||
|
本地执行:
|
||||||
|
`./proxy tcp -p ":33080" -T tcp -P "192.168.22.33:22"`
|
||||||
|
那么访问本地33080端口就是访问192.168.22.33的22端口.
|
||||||
|
|
||||||
|
#### **2.2.普通二级TCP代理**
|
||||||
|

|
||||||
|
VPS(IP:22.22.22.33)执行:
|
||||||
|
`./proxy tcp -p ":33080" -T tcp -P "127.0.0.1:8080"`
|
||||||
|
本地执行:
|
||||||
|
`./proxy tcp -p ":23080" -T tcp -P "22.22.22.33:33080"`
|
||||||
|
那么访问本地23080端口就是访问22.22.22.33的8080端口.
|
||||||
|
|
||||||
|
#### **2.3.普通三级TCP代理**
|
||||||
|
一级TCP代理VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp -p ":38080" -T tcp -P "66.66.66.66:8080"`
|
||||||
|
二级TCP代理VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp -p ":28080" -T tcp -P "22.22.22.22:38080"`
|
||||||
|
三级TCP代理(本地)
|
||||||
|
`./proxy tcp -p ":8080" -T tcp -P "33.33.33.33:28080"`
|
||||||
|
那么访问本地8080端口就是通过加密TCP隧道访问66.66.66.66的8080端口.
|
||||||
|
|
||||||
|
#### **2.4.加密二级TCP代理**
|
||||||
|
VPS(IP:22.22.22.33)执行:
|
||||||
|
`./proxy tcp -t tcp -p ":33080" -T tcp -P "127.0.0.1:8080" -C proxy.crt -K proxy.key`
|
||||||
|
本地执行:
|
||||||
|
`./proxy tcp -p ":23080" -T tls -P "22.22.22.33:33080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地23080端口就是通过加密TCP隧道访问22.22.22.33的8080端口.
|
||||||
|
|
||||||
|
#### **2.5.加密三级TCP代理**
|
||||||
|
一级TCP代理VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp -t tcp -p ":38080" -T tcp -P "66.66.66.66:8080" -C proxy.crt -K proxy.key`
|
||||||
|
二级TCP代理VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp -t tcp -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
三级TCP代理(本地)
|
||||||
|
`./proxy tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地8080端口就是通过加密TCP隧道访问66.66.66.66的8080端口.
|
||||||
|
|
||||||
|
#### **2.6.查看帮助**
|
||||||
|
`./proxy help tcp`
|
||||||
|
|
||||||
|
### **3.UDP代理**
|
||||||
|
|
||||||
|
#### **3.1.普通一级UDP代理**
|
||||||
|
本地执行:
|
||||||
|
`./proxy udp -p ":5353" -T udp -P "8.8.8.8:53"`
|
||||||
|
那么访问本地UDP:5353端口就是访问8.8.8.8的UDP:53端口.
|
||||||
|
|
||||||
|
#### **3.2.普通二级UDP代理**
|
||||||
|
VPS(IP:22.22.22.33)执行:
|
||||||
|
`./proxy tcp -p ":33080" -T udp -P "8.8.8.8:53"`
|
||||||
|
本地执行:
|
||||||
|
`./proxy udp -p ":5353" -T tcp -P "22.22.22.33:33080"`
|
||||||
|
那么访问本地UDP:5353端口就是通过TCP隧道,通过VPS访问8.8.8.8的UDP:53端口.
|
||||||
|
|
||||||
|
#### **3.3.普通三级UDP代理**
|
||||||
|
一级TCP代理VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp -p ":38080" -T udp -P "8.8.8.8:53"`
|
||||||
|
二级TCP代理VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp -p ":28080" -T tcp -P "22.22.22.22:38080"`
|
||||||
|
三级TCP代理(本地)
|
||||||
|
`./proxy udp -p ":5353" -T tcp -P "33.33.33.33:28080"`
|
||||||
|
那么访问本地5353端口就是通过TCP隧道,通过VPS访问8.8.8.8的53端口.
|
||||||
|
|
||||||
|
#### **3.4.加密二级UDP代理**
|
||||||
|
VPS(IP:22.22.22.33)执行:
|
||||||
|
`./proxy tcp -t tcp -p ":33080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key`
|
||||||
|
本地执行:
|
||||||
|
`./proxy udp -p ":5353" -T tls -P "22.22.22.33:33080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地UDP:5353端口就是通过加密TCP隧道,通过VPS访问8.8.8.8的UDP:53端口.
|
||||||
|
|
||||||
|
#### **3.5.加密三级UDP代理**
|
||||||
|
一级TCP代理VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy tcp -t tcp -p ":38080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key`
|
||||||
|
二级TCP代理VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy tcp -t tcp -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
三级TCP代理(本地)
|
||||||
|
`./proxy udp -p ":5353" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地5353端口就是通过加密TCP隧道,通过VPS_01访问8.8.8.8的53端口.
|
||||||
|
|
||||||
|
#### **3.6.查看帮助**
|
||||||
|
`./proxy help udp`
|
||||||
|
|
||||||
|
### **4.内网穿透**
|
||||||
|
#### **4.1、原理说明**
|
||||||
|
内网穿透,分为两个版本,“多链接版本”和“多路复用版本”,一般像web服务这种不是长时间连接的服务建议用“多链接版本”,如果是要保持长时间连接建议使用“多路复用版本”。
|
||||||
|
1. 多链接版本,对应的子命令是tserver,tclient,tbridge。
|
||||||
|
1. 多路复用版本,对应的子命令是server,client,bridge。
|
||||||
|
1. 多链接版本和多路复用版本的参数和使用方式完全一样。
|
||||||
|
1. **多路复用版本的server,client可以开启压缩传输,参数是--c。**
|
||||||
|
1. **server,client要么都开启压缩,要么都不开启,不能只开一个。**
|
||||||
|
|
||||||
|
下面的教程以“多路复用版本”为例子,说明使用方法。
|
||||||
|
内网穿透由三部分组成:client端,server端,bridge端;client和server主动连接bridge端进行桥接.
|
||||||
|
当用户访问server端,流程是:
|
||||||
|
1. 首先server端主动和bridge端建立连接;
|
||||||
|
1. 然后bridge端通知client端连接bridge端和目标端口;
|
||||||
|
1. 然后client端绑定“client端到bridge端”和“client端到目标端口”的连接;
|
||||||
|
1. 然后bridge端把“client过来的连接”与“server端过来的连接”绑定;
|
||||||
|
1. 整个通道建立完成;
|
||||||
|
|
||||||
|
#### **4.2、TCP普通用法**
|
||||||
|
背景:
|
||||||
|
- 公司机器A提供了web服务80端口
|
||||||
|
- 有VPS一个,公网IP:22.22.22.22
|
||||||
|
|
||||||
|
需求:
|
||||||
|
在家里能够通过访问VPS的28080端口访问到公司机器A的80端口
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1. 在vps上执行
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server -r ":28080@:80" -P "127.0.0.1:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 在公司机器A上面执行
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 完成
|
||||||
|
|
||||||
|
#### **4.3、微信接口本地开发**
|
||||||
|
背景:
|
||||||
|
- 自己的笔记本提供了nginx服务80端口
|
||||||
|
- 有VPS一个,公网IP:22.22.22.22
|
||||||
|
|
||||||
|
需求:
|
||||||
|
在微信的开发帐号的网页回调接口配置里面填写地址:http://22.22.22.22/calback.php
|
||||||
|
然后就可以访问到笔记本的80端口下面的calback.php,如果需要绑定域名,可以用自己的域名
|
||||||
|
比如:wx-dev.xxx.com解析到22.22.22.22,然后在自己笔记本的nginx里
|
||||||
|
配置域名wx-dev.xxx.com到具体的目录即可.
|
||||||
|
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1. 在vps上执行,确保vps的80端口没被其它程序占用.
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server -r ":80@:80" -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 在自己笔记本上面执行
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 完成
|
||||||
|
|
||||||
|
#### **4.4、UDP普通用法**
|
||||||
|
背景:
|
||||||
|
- 公司机器A提供了DNS解析服务,UDP:53端口
|
||||||
|
- 有VPS一个,公网IP:22.22.22.22
|
||||||
|
|
||||||
|
需求:
|
||||||
|
在家里能够通过设置本地dns为22.22.22.22,使用公司机器A进行域名解析服务.
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1. 在vps上执行
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server --udp -r ":53@:53" -P "127.0.0.1:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 在公司机器A上面执行
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 完成
|
||||||
|
|
||||||
|
#### **4.5、高级用法一**
|
||||||
|
背景:
|
||||||
|
- 公司机器A提供了web服务80端口
|
||||||
|
- 有VPS一个,公网IP:22.22.22.22
|
||||||
|
|
||||||
|
需求:
|
||||||
|
为了安全,不想在VPS上能够访问到公司机器A,在家里能够通过访问本机的28080端口,
|
||||||
|
通过加密隧道访问到公司机器A的80端口.
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1. 在vps上执行
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 在公司机器A上面执行
|
||||||
|
`./proxy client -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 在家里电脑上执行
|
||||||
|
`./proxy server -r ":28080@:80" -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 完成
|
||||||
|
|
||||||
|
#### **4.6、高级用法二**
|
||||||
|
提示:
|
||||||
|
如果同时有多个client连接到同一个bridge,需要指定不同的key,可以通过--k参数设定,--k可以是任意唯一字符串,
|
||||||
|
只要在同一个bridge上唯一即可.
|
||||||
|
server连接到bridge的时候,如果同时有多个client连接到同一个bridge,需要使用--k参数选择client.
|
||||||
|
暴露多个端口重复-r参数即可.-r格式是:"本地IP:本地端口@clientHOST:client端口".
|
||||||
|
|
||||||
|
背景:
|
||||||
|
- 公司机器A提供了web服务80端口,ftp服务21端口
|
||||||
|
- 有VPS一个,公网IP:22.22.22.22
|
||||||
|
|
||||||
|
需求:
|
||||||
|
在家里能够通过访问VPS的28080端口访问到公司机器A的80端口
|
||||||
|
在家里能够通过访问VPS的29090端口访问到公司机器A的21端口
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1. 在vps上执行
|
||||||
|
`./proxy bridge -p ":33080" -C proxy.crt -K proxy.key`
|
||||||
|
`./proxy server -r ":28080@:80" -r ":29090@:21" --k test -P "127.0.0.1:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 在公司机器A上面执行
|
||||||
|
`./proxy client --k test -P "22.22.22.22:33080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
1. 完成
|
||||||
|
|
||||||
|
#### **4.7.server的-r参数**
|
||||||
|
-r完整格式是:`PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT`
|
||||||
|
|
||||||
|
4.7.1.协议PROTOCOL:tcp或者udp.
|
||||||
|
比如: `-r "udp://:10053@:53" -r "tcp://:10800@:1080" -r ":8080@:80"`
|
||||||
|
如果指定了--udp参数,PROTOCOL默认为udp,那么:`-r ":8080@:80"`默认为udp;
|
||||||
|
如果没有指定--udp参数,PROTOCOL默认为tcp,那么:`-r ":8080@:80"`默认为tcp;
|
||||||
|
|
||||||
|
4.7.2.CLIENT_KEY:默认是default.
|
||||||
|
比如: -r "udp://:10053@[test1]:53" -r "tcp://:10800@[test2]:1080" -r ":8080@:80"
|
||||||
|
如果指定了--k参数,比如--k test,那么:`-r ":8080@:80"`CLIENT_KEY默认为test;
|
||||||
|
如果没有指定--k参数,那么:`-r ":8080@:80"`CLIENT_KEY默认为default;
|
||||||
|
|
||||||
|
4.7.3.LOCAL_IP为空默认是:`0.0.0.0`,CLIENT_LOCAL_HOST为空默认是:`127.0.0.1`;
|
||||||
|
|
||||||
|
#### **4.8.查看帮助**
|
||||||
|
`./proxy help bridge`
|
||||||
|
`./proxy help server`
|
||||||
|
`./proxy help server`
|
||||||
|
|
||||||
|
### **5.SOCKS5代理**
|
||||||
|
提示:SOCKS5代理,支持CONNECT,UDP协议,不支持BIND,支持用户名密码认证.
|
||||||
|
#### **5.1.普通SOCKS5代理**
|
||||||
|
`./proxy socks -t tcp -p "0.0.0.0:38080"`
|
||||||
|
|
||||||
|
#### **5.2.普通二级SOCKS5代理**
|
||||||
|

|
||||||
|
使用本地端口8090,假设上级SOCKS5代理是`22.22.22.22:8080`
|
||||||
|
`./proxy socks -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" `
|
||||||
|
我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理.
|
||||||
|
`./proxy socks -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt`
|
||||||
|
|
||||||
|
#### **5.3.SOCKS二级代理(加密)**
|
||||||
|
一级SOCKS代理(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy socks -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
二级SOCKS代理(本地Linux)
|
||||||
|
`./proxy socks -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地的8080端口就是访问VPS上面的代理端口38080.
|
||||||
|
|
||||||
|
二级SOCKS代理(本地windows)
|
||||||
|
`./proxy.exe socks -t tcp -p ":8080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
然后设置你的windos系统中,需要通过代理上网的程序的代理为socks5模式,地址为:127.0.0.1,端口为:8080,程序即可通过加密通道通过vps上网。
|
||||||
|
|
||||||
|
#### **5.4.SOCKS三级代理(加密)**
|
||||||
|
一级SOCKS代理VPS_01,IP:22.22.22.22
|
||||||
|
`./proxy socks -t tls -p ":38080" -C proxy.crt -K proxy.key`
|
||||||
|
二级SOCKS代理VPS_02,IP:33.33.33.33
|
||||||
|
`./proxy socks -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
三级SOCKS代理(本地)
|
||||||
|
`./proxy socks -t tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key`
|
||||||
|
那么访问本地的8080端口就是访问一级SOCKS代理上面的代理端口38080.
|
||||||
|
|
||||||
|
#### **5.5.SOCKS代理流量强制走上级SOCKS代理**
|
||||||
|
默认情况下,proxy会智能判断一个网站域名是否无法访问,如果无法访问才走上级SOCKS代理.通过--always可以使全部SOCKS代理流量强制走上级SOCKS代理.
|
||||||
|
`./proxy socks --always -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key`
|
||||||
|
|
||||||
|
#### **5.6.SOCKS通过SSH中转**
|
||||||
|
说明:ssh中转的原理是利用了ssh的转发功能,就是你连接上ssh之后,可以通过ssh代理访问目标地址.
|
||||||
|
假设有:vps
|
||||||
|
- IP是2.2.2.2, ssh端口是22, ssh用户名是:user, ssh用户密码是:demo
|
||||||
|
- 用户user的ssh私钥名称是user.key
|
||||||
|
|
||||||
|
##### ***5.6.1 ssh用户名和密码的方式***
|
||||||
|
本地SOCKS5代理28080端口,执行:
|
||||||
|
`./proxy socks -T ssh -P "2.2.2.2:22" -u user -A demo -t tcp -p ":28080"`
|
||||||
|
##### ***5.6.2 ssh用户名和密钥的方式***
|
||||||
|
本地SOCKS5代理28080端口,执行:
|
||||||
|
`./proxy socks -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"`
|
||||||
|
|
||||||
|
那么访问本地的28080端口就是通过VPS访问目标地址.
|
||||||
|
|
||||||
|
#### **5.7.认证**
|
||||||
|
对于socks5代理协议我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定
|
||||||
|
`./proxy socks -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"`
|
||||||
|
多个用户,重复-a参数即可.
|
||||||
|
也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定.
|
||||||
|
`./proxy socks -t tcp -p ":33080" -F auth-file.txt`
|
||||||
|
|
||||||
|
另外,socks5代理还集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址,
|
||||||
|
然后有用户连接的时候,proxy会GET方式请求这url,带上下面四个参数,如果返回HTTP状态码204,代表认证成功
|
||||||
|
其它情况认为认证失败.
|
||||||
|
比如:
|
||||||
|
`./proxy socks -t tcp -p ":33080" --auth-url "http://test.com/auth.php"`
|
||||||
|
用户连接的时候,proxy会GET方式请求这url("http://test.com/auth.php"),
|
||||||
|
带上user,pass,ip,三个参数:
|
||||||
|
http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}
|
||||||
|
user:用户名
|
||||||
|
pass:密码
|
||||||
|
ip:用户的IP,比如:192.168.1.200
|
||||||
|
|
||||||
|
如果没有-a或-F或--auth-url参数,就是关闭认证.
|
||||||
|
|
||||||
|
#### **5.8.KCP协议传输**
|
||||||
|
KCP协议需要-B参数设置一个密码用于加密解密数据
|
||||||
|
|
||||||
|
一级HTTP代理(VPS,IP:22.22.22.22)
|
||||||
|
`./proxy socks -t kcp -p ":38080" -B mypassword`
|
||||||
|
|
||||||
|
二级HTTP代理(本地Linux)
|
||||||
|
`./proxy socks -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword`
|
||||||
|
那么访问本地的8080端口就是访问VPS上面的代理端口38080,数据通过kcp协议传输.
|
||||||
|
|
||||||
|
#### **5.9.查看帮助**
|
||||||
|
`./proxy help socks`
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
- http,socks代理多个上级负载均衡?
|
||||||
|
- http(s)代理增加pac支持?
|
||||||
|
- 欢迎加群反馈...
|
||||||
|
|
||||||
|
### 如何使用源码?
|
||||||
|
建议go1.8,不保证>=1.9能用.
|
||||||
|
cd进入你的go src目录,然后git clone https://github.com/snail007/goproxy.git ./proxy 即可.
|
||||||
|
编译直接:go build
|
||||||
|
运行: go run *.go
|
||||||
|
utils是工具包,service是具体的每个服务类.
|
||||||
|
|
||||||
|
### License
|
||||||
|
Proxy is licensed under GPLv3 license.
|
||||||
|
### Contact
|
||||||
|
QQ交流群:189618940
|
||||||
|
|
||||||
|
|
||||||
|
### Donation
|
||||||
|
如果proxy帮助你解决了很多问题,你可以通过下面的捐赠更好的支持proxy.
|
||||||
|
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/alipay.jpg?raw=true" width="200"/>
|
||||||
|
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/wxpay.jpg?raw=true" width="200"/>
|
||||||
|
|
||||||
|
|
||||||
302
config.go
Executable file
@ -0,0 +1,302 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"proxy/services"
|
||||||
|
"proxy/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
app *kingpin.Application
|
||||||
|
service *services.ServiceItem
|
||||||
|
cmd *exec.Cmd
|
||||||
|
)
|
||||||
|
|
||||||
|
func initConfig() (err error) {
|
||||||
|
//keygen
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
if os.Args[1] == "keygen" {
|
||||||
|
utils.Keygen()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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{}
|
||||||
|
//build srvice args
|
||||||
|
app = kingpin.New("proxy", "happy with proxy")
|
||||||
|
app.Author("snail").Version(APP_VERSION)
|
||||||
|
debug := app.Flag("debug", "debug log output").Default("false").Bool()
|
||||||
|
daemon := app.Flag("daemon", "run proxy in background").Default("false").Bool()
|
||||||
|
forever := app.Flag("forever", "run proxy in forever,fail and retry").Default("false").Bool()
|
||||||
|
logfile := app.Flag("log", "log file path").Default("").String()
|
||||||
|
|
||||||
|
//########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.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.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,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()
|
||||||
|
|
||||||
|
//########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.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")
|
||||||
|
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.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.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('t').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 mode").Default("false").Bool()
|
||||||
|
|
||||||
|
//########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.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('t').Default("2000").Int()
|
||||||
|
muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String()
|
||||||
|
muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp mode").Default("false").Bool()
|
||||||
|
|
||||||
|
//########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('t').Default("2000").Int()
|
||||||
|
muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String()
|
||||||
|
|
||||||
|
//########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.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.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()
|
||||||
|
|
||||||
|
//parse args
|
||||||
|
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||||
|
flags := log.Ldate
|
||||||
|
if *debug {
|
||||||
|
flags |= log.Lshortfile | log.Lmicroseconds
|
||||||
|
} else {
|
||||||
|
flags |= log.Ltime
|
||||||
|
}
|
||||||
|
log.SetFlags(flags)
|
||||||
|
|
||||||
|
if *logfile != "" {
|
||||||
|
f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||||
|
if e != nil {
|
||||||
|
log.Fatal(e)
|
||||||
|
}
|
||||||
|
log.SetOutput(f)
|
||||||
|
}
|
||||||
|
if *daemon {
|
||||||
|
args := []string{}
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
|
if arg != "--daemon" {
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd = exec.Command(os.Args[0], args...)
|
||||||
|
cmd.Start()
|
||||||
|
f := ""
|
||||||
|
if *forever {
|
||||||
|
f = "forever "
|
||||||
|
}
|
||||||
|
log.Printf("%s%s [PID] %d running...\n", f, os.Args[0], cmd.Process.Pid)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if *forever {
|
||||||
|
args := []string{}
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
|
if arg != "--forever" {
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
cmd = exec.Command(os.Args[0], args...)
|
||||||
|
cmdReaderStderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmdReader, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(cmdReader)
|
||||||
|
scannerStdErr := bufio.NewScanner(cmdReaderStderr)
|
||||||
|
go func() {
|
||||||
|
for scanner.Scan() {
|
||||||
|
fmt.Println(scanner.Text())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for scannerStdErr.Scan() {
|
||||||
|
fmt.Println(scannerStdErr.Text())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pid := cmd.Process.Pid
|
||||||
|
log.Printf("worker %s [PID] %d running...\n", os.Args[0], pid)
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
log.Printf("ERR:%s,restarting...", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("worker %s [PID] %d unexpected exited, restarting...\n", os.Args[0], pid)
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *logfile == "" {
|
||||||
|
poster()
|
||||||
|
}
|
||||||
|
//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("server", services.NewMuxServerManager(), muxServerArgs)
|
||||||
|
services.Regist("client", services.NewMuxClient(), muxClientArgs)
|
||||||
|
services.Regist("bridge", services.NewMuxBridge(), muxBridgeArgs)
|
||||||
|
services.Regist("socks", services.NewSocks(), socksArgs)
|
||||||
|
service, err = services.Run(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("run service [%s] fail, ERR:%s", serviceName, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func poster() {
|
||||||
|
fmt.Printf(`
|
||||||
|
######## ######## ####### ## ## ## ##
|
||||||
|
## ## ## ## ## ## ## ## ## ##
|
||||||
|
## ## ## ## ## ## ## ## ####
|
||||||
|
######## ######## ## ## ### ##
|
||||||
|
## ## ## ## ## ## ## ##
|
||||||
|
## ## ## ## ## ## ## ##
|
||||||
|
## ## ## ####### ## ## ##
|
||||||
|
|
||||||
|
v%s`+" by snail , blog : http://www.host900.com/\n\n", APP_VERSION)
|
||||||
|
}
|
||||||
456
direct
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
07073.com
|
||||||
|
10010.com
|
||||||
|
100ye.com
|
||||||
|
114la.com
|
||||||
|
115.com
|
||||||
|
120ask.com
|
||||||
|
126.com
|
||||||
|
126.net
|
||||||
|
1616.net
|
||||||
|
163.com
|
||||||
|
17173.com
|
||||||
|
1778.com
|
||||||
|
178.com
|
||||||
|
17u.com
|
||||||
|
19lou.com
|
||||||
|
1o26.com
|
||||||
|
1ting.com
|
||||||
|
21cn.com
|
||||||
|
2345.com
|
||||||
|
265.com
|
||||||
|
265g.com
|
||||||
|
28.com
|
||||||
|
28tui.com
|
||||||
|
2hua.com
|
||||||
|
2mdn.net
|
||||||
|
315che.com
|
||||||
|
3366.com
|
||||||
|
360buy.com
|
||||||
|
360buyimg.com
|
||||||
|
360doc.com
|
||||||
|
36kr.com
|
||||||
|
39.net
|
||||||
|
3dmgame.com
|
||||||
|
4399.com
|
||||||
|
4738.com
|
||||||
|
500wan.com
|
||||||
|
51.com
|
||||||
|
51.la
|
||||||
|
5173.com
|
||||||
|
51auto.com
|
||||||
|
51buy.com
|
||||||
|
51cto.com
|
||||||
|
51fanli.com
|
||||||
|
51job.com
|
||||||
|
52kmh.com
|
||||||
|
52pk.net
|
||||||
|
52tlbb.com
|
||||||
|
53kf.com
|
||||||
|
55bbs.com
|
||||||
|
55tuan.com
|
||||||
|
56.com
|
||||||
|
58.com
|
||||||
|
591hx.com
|
||||||
|
5d6d.net
|
||||||
|
61.com
|
||||||
|
70e.com
|
||||||
|
777wyx.com
|
||||||
|
778669.com
|
||||||
|
7c.com
|
||||||
|
7k7k.com
|
||||||
|
88db.com
|
||||||
|
91.com
|
||||||
|
99bill.com
|
||||||
|
a135.net
|
||||||
|
abang.com
|
||||||
|
abchina.com
|
||||||
|
ad1111.com
|
||||||
|
admin5.com
|
||||||
|
adnxs.com
|
||||||
|
adobe.com
|
||||||
|
adroll.com
|
||||||
|
ads8.com
|
||||||
|
adsame.com
|
||||||
|
adsonar.com
|
||||||
|
adtechus.com
|
||||||
|
aibang.com
|
||||||
|
aifang.com
|
||||||
|
aili.com
|
||||||
|
aipai.com
|
||||||
|
aizhan.com
|
||||||
|
ali213.net
|
||||||
|
alibaba.com
|
||||||
|
alicdn.com
|
||||||
|
aliexpress.com
|
||||||
|
alimama.com
|
||||||
|
alipay.com
|
||||||
|
alipayobjects.com
|
||||||
|
alisoft.com
|
||||||
|
alivv.com
|
||||||
|
aliyun.com
|
||||||
|
allyes.com
|
||||||
|
amazon.com
|
||||||
|
anjuke.com
|
||||||
|
anzhi.com
|
||||||
|
aol.com
|
||||||
|
apple.com
|
||||||
|
arpg2.com
|
||||||
|
atdmt.com
|
||||||
|
b2b168.com
|
||||||
|
babytree.com
|
||||||
|
baidu.com
|
||||||
|
baihe.com
|
||||||
|
baixing.com
|
||||||
|
bankcomm.com
|
||||||
|
baomihua.com
|
||||||
|
bdimg.com
|
||||||
|
bdstatic.com
|
||||||
|
bendibao.com
|
||||||
|
betrad.com
|
||||||
|
bilibili.tv
|
||||||
|
bing.com
|
||||||
|
bitauto.com
|
||||||
|
blog.163.com
|
||||||
|
blogchina.com
|
||||||
|
blueidea.com
|
||||||
|
bluekai.com
|
||||||
|
booksky.org
|
||||||
|
caixin.com
|
||||||
|
ccb.com
|
||||||
|
ccidnet.com
|
||||||
|
cctv*.com
|
||||||
|
china.com
|
||||||
|
chinabyte.com
|
||||||
|
chinahr.com
|
||||||
|
chinanews.com
|
||||||
|
chinaw3.com
|
||||||
|
chinaz.com
|
||||||
|
chuangelm.com
|
||||||
|
ci123.com
|
||||||
|
cmbchina.com
|
||||||
|
cnbeta.com
|
||||||
|
cnblogs.com
|
||||||
|
cncn.com
|
||||||
|
cnhubei.com
|
||||||
|
cnki.net
|
||||||
|
cnmo.com
|
||||||
|
cnxad.com
|
||||||
|
cnzz.com
|
||||||
|
cocoren.com
|
||||||
|
compete.com
|
||||||
|
comsenz.com
|
||||||
|
coo8.com
|
||||||
|
cqnews.net
|
||||||
|
crsky.com
|
||||||
|
csdn.net
|
||||||
|
ct10000.com
|
||||||
|
ctrip.com
|
||||||
|
dangdang.com
|
||||||
|
daqi.com
|
||||||
|
dayoo.com
|
||||||
|
dbank.com
|
||||||
|
ddmap.com
|
||||||
|
dedecms.com
|
||||||
|
dh818.com
|
||||||
|
diandian.com
|
||||||
|
dianping.com
|
||||||
|
discuz.net
|
||||||
|
doc88.com
|
||||||
|
docin.com
|
||||||
|
donews.com
|
||||||
|
dospy.com
|
||||||
|
douban.com
|
||||||
|
douban.fm
|
||||||
|
doubleclick.com
|
||||||
|
doubleclick.net
|
||||||
|
duba.net
|
||||||
|
duote.com
|
||||||
|
duowan.com
|
||||||
|
dzwww.com
|
||||||
|
eastday.com
|
||||||
|
eastmoney.com
|
||||||
|
ebay.com
|
||||||
|
elong.com
|
||||||
|
ename.net
|
||||||
|
etao.com
|
||||||
|
exam8.com
|
||||||
|
eye.rs
|
||||||
|
fantong.com
|
||||||
|
fastcdn.com
|
||||||
|
fblife.com
|
||||||
|
fengniao.com
|
||||||
|
fenzhi.com
|
||||||
|
flickr.com
|
||||||
|
fobshanghai.com
|
||||||
|
ftuan.com
|
||||||
|
funshion.com
|
||||||
|
fx120.net
|
||||||
|
game3737.com
|
||||||
|
gamersky.com
|
||||||
|
gamestlbb.com
|
||||||
|
gamesville.com
|
||||||
|
ganji.com
|
||||||
|
gfan.com
|
||||||
|
gongchang.com
|
||||||
|
google-analytics.com
|
||||||
|
gougou.com
|
||||||
|
gtimg.com
|
||||||
|
hao123.com
|
||||||
|
haodf.com
|
||||||
|
harrenmedianetwork.com
|
||||||
|
hc360.com
|
||||||
|
hefei.cc
|
||||||
|
hf365.com
|
||||||
|
hiapk.com
|
||||||
|
hichina.com
|
||||||
|
homeinns.com
|
||||||
|
hotsales.net
|
||||||
|
house365.com
|
||||||
|
huaban.com
|
||||||
|
huanqiu.com
|
||||||
|
hudong.com
|
||||||
|
hupu.com
|
||||||
|
iask.com
|
||||||
|
iciba.com
|
||||||
|
icson.com
|
||||||
|
ifeng.com
|
||||||
|
iloveyouxi.com
|
||||||
|
im286.com
|
||||||
|
imanhua.com
|
||||||
|
img.cctvpic.com
|
||||||
|
imrworldwide.com
|
||||||
|
invitemedia.com
|
||||||
|
ip138.com
|
||||||
|
ipinyou.com
|
||||||
|
iqilu.com
|
||||||
|
iqiyi.com
|
||||||
|
irs01.com
|
||||||
|
irs01.net
|
||||||
|
it168.com
|
||||||
|
iteye.com
|
||||||
|
iyaya.com
|
||||||
|
jb51.net
|
||||||
|
jiathis.com
|
||||||
|
jiayuan.com
|
||||||
|
jing.fm
|
||||||
|
jinti.com
|
||||||
|
jqw.com
|
||||||
|
jumei.com
|
||||||
|
jxedt.com
|
||||||
|
jysq.net
|
||||||
|
kaixin001.com
|
||||||
|
kandian.com
|
||||||
|
kdnet.net
|
||||||
|
kimiss.com
|
||||||
|
ku6.com
|
||||||
|
ku6cdn.com
|
||||||
|
ku6img.com
|
||||||
|
kuaidi100.com
|
||||||
|
kugou.com
|
||||||
|
l99.com
|
||||||
|
lady8844.com
|
||||||
|
lafaso.com
|
||||||
|
lashou.com
|
||||||
|
legolas-media.com
|
||||||
|
lehecai.com
|
||||||
|
leho.com
|
||||||
|
letv.com
|
||||||
|
liebiao.com
|
||||||
|
lietou.com
|
||||||
|
linezing.com
|
||||||
|
linkedin.com
|
||||||
|
live.com
|
||||||
|
longhoo.net
|
||||||
|
lusongsong.com
|
||||||
|
lxdns.com
|
||||||
|
lycos.com
|
||||||
|
lygo.com
|
||||||
|
m18.com
|
||||||
|
m1905.com
|
||||||
|
made-in-china.com
|
||||||
|
makepolo.com
|
||||||
|
mangocity.com
|
||||||
|
manzuo.com
|
||||||
|
mapbar.com
|
||||||
|
mathtag.com
|
||||||
|
mediaplex.com
|
||||||
|
mediav.com
|
||||||
|
meilele.com
|
||||||
|
meilishuo.com
|
||||||
|
meishichina.com
|
||||||
|
meituan.com
|
||||||
|
meizu.com
|
||||||
|
miaozhen.com
|
||||||
|
microsoft.com
|
||||||
|
miercn.com
|
||||||
|
mlt01.com
|
||||||
|
mmcdn.cn
|
||||||
|
mmstat.com
|
||||||
|
mnwan.com
|
||||||
|
mogujie.com
|
||||||
|
mookie1.com
|
||||||
|
moonbasa.com
|
||||||
|
mop.com
|
||||||
|
mosso.com
|
||||||
|
mplife.com
|
||||||
|
msn.com
|
||||||
|
mtime.com
|
||||||
|
mumayi.com
|
||||||
|
mydrivers.com
|
||||||
|
net114.com
|
||||||
|
netease.com
|
||||||
|
newsmth.net
|
||||||
|
nipic.com
|
||||||
|
nowec.com
|
||||||
|
nuomi.com
|
||||||
|
oadz.com
|
||||||
|
oeeee.com
|
||||||
|
onetad.com
|
||||||
|
onlinedown.net
|
||||||
|
onlylady.com
|
||||||
|
oschina.net
|
||||||
|
otwan.com
|
||||||
|
paipai.com
|
||||||
|
paypal.com
|
||||||
|
pchome.net
|
||||||
|
pcpop.com
|
||||||
|
pengyou.com
|
||||||
|
php100.com
|
||||||
|
phpwind.net
|
||||||
|
pingan.com
|
||||||
|
pixlr.com
|
||||||
|
pp.cc
|
||||||
|
ppstream.com
|
||||||
|
pptv.com
|
||||||
|
ptlogin2.qq.com
|
||||||
|
pubmatic.com
|
||||||
|
q150.com
|
||||||
|
qianlong.com
|
||||||
|
qidian.com
|
||||||
|
qingdaonews.com
|
||||||
|
qire123.com
|
||||||
|
qiushibaike.com
|
||||||
|
qiyou.com
|
||||||
|
qjy168.com
|
||||||
|
qq.com
|
||||||
|
qq937.com
|
||||||
|
qstatic.com
|
||||||
|
quantserve.com
|
||||||
|
qunar.com
|
||||||
|
rakuten.co.jp
|
||||||
|
readnovel.com
|
||||||
|
renren.com
|
||||||
|
rtbidder.net
|
||||||
|
scanscout.com
|
||||||
|
scorecardresearch.com
|
||||||
|
sdo.com
|
||||||
|
seowhy.com
|
||||||
|
serving-sys.com
|
||||||
|
sf-express.com
|
||||||
|
shangdu.com
|
||||||
|
si.kz
|
||||||
|
sina.com
|
||||||
|
sinahk.net
|
||||||
|
sinajs.com
|
||||||
|
smzdm.com
|
||||||
|
snyu.com
|
||||||
|
sodu.org
|
||||||
|
sogou.com
|
||||||
|
sohu.com
|
||||||
|
soku.com
|
||||||
|
sootoo.com
|
||||||
|
soso.com
|
||||||
|
soufun.com
|
||||||
|
sourceforge.net
|
||||||
|
staticsdo.com
|
||||||
|
stockstar.com
|
||||||
|
sttlbb.com
|
||||||
|
suning.com
|
||||||
|
szhome.com
|
||||||
|
sznews.com
|
||||||
|
tangdou.com
|
||||||
|
tanx.com
|
||||||
|
tao123.com
|
||||||
|
taobao.com
|
||||||
|
taobaocdn.com
|
||||||
|
tbcdn.cn
|
||||||
|
tdimg.com
|
||||||
|
tenpay.com
|
||||||
|
tgbus.com
|
||||||
|
theplanet.com
|
||||||
|
thethirdmedia.com
|
||||||
|
tiancity.com
|
||||||
|
tianji.com
|
||||||
|
tiao8.info
|
||||||
|
tiexue.net
|
||||||
|
titan24.com
|
||||||
|
tmall.com
|
||||||
|
tom.com
|
||||||
|
toocle.com
|
||||||
|
tremormedia.com
|
||||||
|
tuan800.com
|
||||||
|
tudou.com
|
||||||
|
tudouui.com
|
||||||
|
tui18.com
|
||||||
|
tuniu.com
|
||||||
|
twcczhu.com
|
||||||
|
u17.com
|
||||||
|
ucjoy.com
|
||||||
|
ulink.cc
|
||||||
|
uniontoufang.com
|
||||||
|
up2c.com
|
||||||
|
uuu9.com
|
||||||
|
uuzu.com
|
||||||
|
vancl.com
|
||||||
|
verycd.com
|
||||||
|
vipshop.com
|
||||||
|
vizu.com
|
||||||
|
vjia.com
|
||||||
|
weibo.com
|
||||||
|
weiphone.com
|
||||||
|
west263.com
|
||||||
|
whlongda.com
|
||||||
|
wrating.com
|
||||||
|
wumii.com
|
||||||
|
xiami.com
|
||||||
|
xiaomi.com
|
||||||
|
xiazaiba.com
|
||||||
|
xici.net
|
||||||
|
xinhuanet.com
|
||||||
|
xinnet.com
|
||||||
|
xitek.com
|
||||||
|
xiu.com
|
||||||
|
xunlei.com
|
||||||
|
xyxy.net
|
||||||
|
yahoo.co.jp
|
||||||
|
yahoo.com
|
||||||
|
yaolan.com
|
||||||
|
yesky.com
|
||||||
|
yieldmanager.com
|
||||||
|
yihaodian.com
|
||||||
|
yingjiesheng.com
|
||||||
|
yinyuetai.com
|
||||||
|
yiqifa.com
|
||||||
|
ykimg.com
|
||||||
|
ynet.com
|
||||||
|
yoka.com
|
||||||
|
yolk7.com
|
||||||
|
youboy.com
|
||||||
|
youdao.com
|
||||||
|
yougou.com
|
||||||
|
youku.com
|
||||||
|
youshang.com
|
||||||
|
yupoo.com
|
||||||
|
yxlady.com
|
||||||
|
yyets.com
|
||||||
|
zhaodao123.com
|
||||||
|
zhaopin.com
|
||||||
|
zhenai.com
|
||||||
|
zhibo8.cc
|
||||||
|
zhihu.com
|
||||||
|
zhubajie.com
|
||||||
|
zongheng.com
|
||||||
|
zoosnet.net
|
||||||
|
zqgame.com
|
||||||
|
ztgame.com
|
||||||
|
zx915.com
|
||||||
@ -1,40 +0,0 @@
|
|||||||
这里以vps centos 64位为例子
|
|
||||||
Linux 部分
|
|
||||||
1.Putty工具(或其他工具)
|
|
||||||
root登入
|
|
||||||
2.下载批量命令文件install_auto.sh(64位的话直接执行这个命令即可)
|
|
||||||
#curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.sh | bash
|
|
||||||
注意
|
|
||||||
这里的install_auto.sh 源码可以下载修改proxy版本,保存后执行.
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image001.png?raw=true"/>
|
|
||||||
3.修改/etc/proxy/proxy.toml配置文件
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image002.png?raw=true"/>
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image003.png?raw=true"/>
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image004.png?raw=true"/>
|
|
||||||
#/usr/bin/proxyd status
|
|
||||||
如果未运行那么执行调试命令:/usr/bin/proxy
|
|
||||||
如果一切正常,可以使用proxyd命令管理proxy,执行 proxyd 可以查看用法.
|
|
||||||
后台启动proxy: proxyd start
|
|
||||||
4.下载证书加密文件/etc/proxy/proxy.crt和/etc/proxy/proxy.key到windows
|
|
||||||
Windows部分
|
|
||||||
5.https://github.com/snail007/goproxy/releases 下载对应windows版本
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image005.jpg?raw=true"/>
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image006.png?raw=true"/>
|
|
||||||
我的是d:盘
|
|
||||||
6.修改windows下的proxy.toml vps服务ip和上面设置的端口哦
|
|
||||||
<img src="https://github.com/snail007/goproxy/blob/master/docs/images/image007.png?raw=true"/>
|
|
||||||
然后运行proxy.exe即可.
|
|
||||||
这时候浏览器代理服务器就是127.0.0.1:9501啦,完毕!
|
|
||||||
|
|
||||||
要隐藏windows命令用工具下载RunHiddenConsole.exe 写个bat文件都放proxy目录下就行
|
|
||||||
Start.bat
|
|
||||||
|
|
||||||
@echo off
|
|
||||||
echo Starting
|
|
||||||
RunHiddenConsole D:/proxy/proxy.exe
|
|
||||||
|
|
||||||
Stop.bat
|
|
||||||
@echo off
|
|
||||||
echo Stopping
|
|
||||||
taskkill /F /IM proxy.exe > nul
|
|
||||||
exit
|
|
||||||
BIN
docs/images/1.1.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/images/2.1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/images/2.2.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/5.2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/alipay.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 11 KiB |
BIN
docs/images/logo.jpg
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
docs/images/wxpay.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
19
install.sh
@ -1,25 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
if [ -e /tmp/proxy ]; then
|
|
||||||
rm -rf /tmp/proxy
|
|
||||||
fi
|
|
||||||
mkdir /tmp/proxy
|
|
||||||
cd /tmp/proxy
|
|
||||||
# install monexec
|
|
||||||
tar zxvf monexec_0.1.1_linux_amd64.tar.gz
|
|
||||||
cd monexec_0.1.1_linux_amd64
|
|
||||||
cp monexec /usr/bin/
|
|
||||||
chmod +x /usr/bin/monexec
|
|
||||||
cd ..
|
|
||||||
# #install proxy
|
# #install proxy
|
||||||
tar zxvf proxy-linux-amd64.tar.gz
|
tar zxvf proxy-linux-amd64.tar.gz
|
||||||
cp proxy /usr/bin/
|
cp proxy /usr/bin/
|
||||||
cp proxyd /usr/bin/
|
|
||||||
chmod +x /usr/bin/proxy
|
chmod +x /usr/bin/proxy
|
||||||
chmod +x /usr/bin/proxyd
|
|
||||||
if [ ! -e /etc/proxy ]; then
|
if [ ! -e /etc/proxy ]; then
|
||||||
mkdir /etc/proxy
|
mkdir /etc/proxy
|
||||||
cp proxy.toml /etc/proxy/
|
cp blocked /etc/proxy
|
||||||
|
cp direct /etc/proxy
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -e /etc/proxy/proxy.crt ]; then
|
if [ ! -e /etc/proxy/proxy.crt ]; then
|
||||||
@ -28,4 +17,4 @@ if [ ! -e /etc/proxy/proxy.crt ]; then
|
|||||||
fi
|
fi
|
||||||
rm -rf /tmp/proxy
|
rm -rf /tmp/proxy
|
||||||
echo "install done"
|
echo "install done"
|
||||||
proxyd
|
proxy help
|
||||||
|
|||||||
@ -5,24 +5,16 @@ if [ -e /tmp/proxy ]; then
|
|||||||
fi
|
fi
|
||||||
mkdir /tmp/proxy
|
mkdir /tmp/proxy
|
||||||
cd /tmp/proxy
|
cd /tmp/proxy
|
||||||
wget https://github.com/reddec/monexec/releases/download/v0.1.1/monexec_0.1.1_linux_amd64.tar.gz
|
wget https://github.com/snail007/goproxy/releases/download/v4.2/proxy-linux-amd64.tar.gz
|
||||||
wget https://github.com/snail007/goproxy/releases/download/v2.1/proxy-linux-amd64.tar.gz
|
|
||||||
|
|
||||||
# install monexec
|
|
||||||
tar zxvf monexec_0.1.1_linux_amd64.tar.gz
|
|
||||||
cd monexec_0.1.1_linux_amd64
|
|
||||||
cp monexec /usr/bin/
|
|
||||||
chmod +x /usr/bin/monexec
|
|
||||||
cd ..
|
|
||||||
# #install proxy
|
# #install proxy
|
||||||
tar zxvf proxy-linux-amd64.tar.gz
|
tar zxvf proxy-linux-amd64.tar.gz
|
||||||
cp proxy /usr/bin/
|
cp proxy /usr/bin/
|
||||||
cp proxyd /usr/bin/
|
|
||||||
chmod +x /usr/bin/proxy
|
chmod +x /usr/bin/proxy
|
||||||
chmod +x /usr/bin/proxyd
|
|
||||||
if [ ! -e /etc/proxy ]; then
|
if [ ! -e /etc/proxy ]; then
|
||||||
mkdir /etc/proxy
|
mkdir /etc/proxy
|
||||||
cp proxy.toml /etc/proxy/
|
cp blocked /etc/proxy
|
||||||
|
cp direct /etc/proxy
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -e /etc/proxy/proxy.crt ]; then
|
if [ ! -e /etc/proxy/proxy.crt ]; then
|
||||||
@ -31,4 +23,4 @@ if [ ! -e /etc/proxy/proxy.crt ]; then
|
|||||||
fi
|
fi
|
||||||
rm -rf /tmp/proxy
|
rm -rf /tmp/proxy
|
||||||
echo "install done"
|
echo "install done"
|
||||||
proxyd
|
proxy help
|
||||||
|
|||||||
3
keygen.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
openssl genrsa -out proxy.key 2048
|
||||||
|
openssl req -new -key proxy.key -x509 -days 3650 -out proxy.crt -subj /C=CN/ST=BJ/O="Localhost Ltd"/CN=proxy
|
||||||
47
main.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"proxy/services"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const APP_VERSION = "4.2"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := initConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("err : %s", err)
|
||||||
|
}
|
||||||
|
if service != nil && service.S != nil {
|
||||||
|
Clean(&service.S)
|
||||||
|
} else {
|
||||||
|
Clean(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Clean(s *services.Service) {
|
||||||
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
cleanupDone := make(chan bool)
|
||||||
|
signal.Notify(signalChan,
|
||||||
|
os.Interrupt,
|
||||||
|
syscall.SIGHUP,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
syscall.SIGQUIT)
|
||||||
|
go func() {
|
||||||
|
for _ = range signalChan {
|
||||||
|
log.Println("Received an interrupt, stopping services...")
|
||||||
|
if s != nil && *s != nil {
|
||||||
|
(*s).Clean()
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
log.Printf("clean process %d", cmd.Process.Pid)
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
cleanupDone <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
<-cleanupDone
|
||||||
|
}
|
||||||
72
release.sh
Executable file
@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
VER="4.2"
|
||||||
|
RELEASE="release-${VER}"
|
||||||
|
rm -rf .cert
|
||||||
|
mkdir .cert
|
||||||
|
go build
|
||||||
|
cd .cert
|
||||||
|
../proxy keygen
|
||||||
|
cd ..
|
||||||
|
rm -rf ${RELEASE}
|
||||||
|
mkdir ${RELEASE}
|
||||||
|
#linux
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-linux-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-linux-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build && tar zcfv "${RELEASE}/proxy-linux-arm-v6.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=6 go build && tar zcfv "${RELEASE}/proxy-linux-arm64-v6.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm-v7.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=7 go build && tar zcfv "${RELEASE}/proxy-linux-arm64-v7.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build && tar zcfv "${RELEASE}/proxy-linux-arm-v5.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=5 go build && tar zcfv "${RELEASE}/proxy-linux-arm64-v5.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build && tar zcfv "${RELEASE}/proxy-linux-mips.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build && tar zcfv "${RELEASE}/proxy-linux-mips64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build && tar zcfv "${RELEASE}/proxy-linux-mips64le.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build && tar zcfv "${RELEASE}/proxy-linux-mipsle.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64 go build && tar zcfv "${RELEASE}/proxy-linux-ppc64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build && tar zcfv "${RELEASE}/proxy-linux-ppc64le.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build && tar zcfv "${RELEASE}/proxy-linux-s390x.tar.gz" proxy direct blocked
|
||||||
|
#android
|
||||||
|
CGO_ENABLED=0 GOOS=android GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-android-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=android GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-android-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=android GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-android-arm.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=android GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-android-arm64.tar.gz" proxy direct blocked
|
||||||
|
#darwin
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-darwin-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-darwin-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-darwin-arm.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build && tar zcfv "${RELEASE}/proxy-darwin-arm64.tar.gz" proxy direct blocked
|
||||||
|
#dragonfly
|
||||||
|
CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-dragonfly-amd64.tar.gz" proxy direct blocked
|
||||||
|
#freebsd
|
||||||
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-freebsd-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-freebsd-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-freebsd-arm.tar.gz" proxy direct blocked
|
||||||
|
#nacl
|
||||||
|
CGO_ENABLED=0 GOOS=nacl GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-nacl-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=nacl GOARCH=amd64p32 go build && tar zcfv "${RELEASE}/proxy-nacl-amd64p32.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=nacl GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-nacl-arm.tar.gz" proxy direct blocked
|
||||||
|
#netbsd
|
||||||
|
CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-netbsd-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-netbsd-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-netbsd-arm.tar.gz" proxy direct blocked
|
||||||
|
#openbsd
|
||||||
|
CGO_ENABLED=0 GOOS=openbsd GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-openbsd-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-openbsd-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=openbsd GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-openbsd-arm.tar.gz" proxy direct blocked
|
||||||
|
#plan9
|
||||||
|
CGO_ENABLED=0 GOOS=plan9 GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-plan9-386.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=plan9 GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-plan9-amd64.tar.gz" proxy direct blocked
|
||||||
|
CGO_ENABLED=0 GOOS=plan9 GOARCH=arm go build && tar zcfv "${RELEASE}/proxy-plan9-arm.tar.gz" proxy direct blocked
|
||||||
|
#solaris
|
||||||
|
CGO_ENABLED=0 GOOS=solaris GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-solaris-amd64.tar.gz" proxy direct blocked
|
||||||
|
#windows
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build && tar zcfv "${RELEASE}/proxy-windows-386.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build && tar zcfv "${RELEASE}/proxy-windows-amd64.tar.gz" proxy.exe direct blocked .cert/proxy.crt .cert/proxy.key
|
||||||
|
|
||||||
|
rm -rf proxy proxy.exe .cert
|
||||||
|
|
||||||
|
#todo
|
||||||
|
#1.release.sh VER="xxx"
|
||||||
|
#2.main.go APP_VERSION="xxx"
|
||||||
|
#3.install_auto.sh goproxy/releases/download/vxxx
|
||||||
|
#4.README goproxy/releases/download/vxxx
|
||||||
197
services/args.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import "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()
|
||||||
|
|
||||||
|
const (
|
||||||
|
TYPE_TCP = "tcp"
|
||||||
|
TYPE_UDP = "udp"
|
||||||
|
TYPE_HTTP = "http"
|
||||||
|
TYPE_TLS = "tls"
|
||||||
|
TYPE_KCP = "kcp"
|
||||||
|
CONN_CLIENT_CONTROL = uint8(1)
|
||||||
|
CONN_CLIENT_HEARBEAT = uint8(2)
|
||||||
|
CONN_SERVER_HEARBEAT = uint8(3)
|
||||||
|
CONN_SERVER = uint8(4)
|
||||||
|
CONN_CLIENT = uint8(5)
|
||||||
|
CONN_SERVER_MUX = uint8(6)
|
||||||
|
CONN_CLIENT_MUX = uint8(7)
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxServerArgs struct {
|
||||||
|
Parent *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
|
||||||
|
}
|
||||||
|
type MuxClientArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Key *string
|
||||||
|
Timeout *int
|
||||||
|
IsCompress *bool
|
||||||
|
}
|
||||||
|
type MuxBridgeArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
Timeout *int
|
||||||
|
IsCompress *bool
|
||||||
|
}
|
||||||
|
type TunnelServerArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
IsUDP *bool
|
||||||
|
Key *string
|
||||||
|
Remote *string
|
||||||
|
Timeout *int
|
||||||
|
Route *[]string
|
||||||
|
Mgr *TunnelServerManager
|
||||||
|
Mux *bool
|
||||||
|
}
|
||||||
|
type TunnelClientArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Key *string
|
||||||
|
Timeout *int
|
||||||
|
Mux *bool
|
||||||
|
}
|
||||||
|
type TunnelBridgeArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
Timeout *int
|
||||||
|
Mux *bool
|
||||||
|
}
|
||||||
|
type TCPArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
ParentType *string
|
||||||
|
LocalType *string
|
||||||
|
Timeout *int
|
||||||
|
PoolSize *int
|
||||||
|
CheckParentInterval *int
|
||||||
|
KCPMethod *string
|
||||||
|
KCPKey *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
Always *bool
|
||||||
|
HTTPTimeout *int
|
||||||
|
Interval *int
|
||||||
|
Blocked *string
|
||||||
|
Direct *string
|
||||||
|
AuthFile *string
|
||||||
|
Auth *[]string
|
||||||
|
AuthURL *string
|
||||||
|
AuthURLOkCode *int
|
||||||
|
AuthURLTimeout *int
|
||||||
|
AuthURLRetry *int
|
||||||
|
ParentType *string
|
||||||
|
LocalType *string
|
||||||
|
Timeout *int
|
||||||
|
PoolSize *int
|
||||||
|
CheckParentInterval *int
|
||||||
|
SSHKeyFile *string
|
||||||
|
SSHKeyFileSalt *string
|
||||||
|
SSHPassword *string
|
||||||
|
SSHUser *string
|
||||||
|
SSHKeyBytes []byte
|
||||||
|
SSHAuthMethod ssh.AuthMethod
|
||||||
|
KCPMethod *string
|
||||||
|
KCPKey *string
|
||||||
|
LocalIPS *[]string
|
||||||
|
}
|
||||||
|
type UDPArgs struct {
|
||||||
|
Parent *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
Local *string
|
||||||
|
ParentType *string
|
||||||
|
Timeout *int
|
||||||
|
PoolSize *int
|
||||||
|
CheckParentInterval *int
|
||||||
|
}
|
||||||
|
type SocksArgs struct {
|
||||||
|
Parent *string
|
||||||
|
ParentType *string
|
||||||
|
Local *string
|
||||||
|
LocalType *string
|
||||||
|
CertFile *string
|
||||||
|
KeyFile *string
|
||||||
|
CertBytes []byte
|
||||||
|
KeyBytes []byte
|
||||||
|
SSHKeyFile *string
|
||||||
|
SSHKeyFileSalt *string
|
||||||
|
SSHPassword *string
|
||||||
|
SSHUser *string
|
||||||
|
SSHKeyBytes []byte
|
||||||
|
SSHAuthMethod ssh.AuthMethod
|
||||||
|
Timeout *int
|
||||||
|
Always *bool
|
||||||
|
Interval *int
|
||||||
|
Blocked *string
|
||||||
|
Direct *string
|
||||||
|
AuthFile *string
|
||||||
|
Auth *[]string
|
||||||
|
AuthURL *string
|
||||||
|
AuthURLOkCode *int
|
||||||
|
AuthURLTimeout *int
|
||||||
|
AuthURLRetry *int
|
||||||
|
KCPMethod *string
|
||||||
|
KCPKey *string
|
||||||
|
UDPParent *string
|
||||||
|
UDPLocal *string
|
||||||
|
LocalIPS *[]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TCPArgs) 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"
|
||||||
|
}
|
||||||
386
services/http.go
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTP struct {
|
||||||
|
outPool utils.OutPool
|
||||||
|
cfg HTTPArgs
|
||||||
|
checker utils.Checker
|
||||||
|
basicAuth utils.BasicAuth
|
||||||
|
sshClient *ssh.Client
|
||||||
|
lockChn chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTP() Service {
|
||||||
|
return &HTTP{
|
||||||
|
outPool: utils.OutPool{},
|
||||||
|
cfg: HTTPArgs{},
|
||||||
|
checker: utils.Checker{},
|
||||||
|
basicAuth: utils.BasicAuth{},
|
||||||
|
lockChn: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *HTTP) CheckArgs() {
|
||||||
|
var err error
|
||||||
|
if *s.cfg.Parent != "" && *s.cfg.ParentType == "" {
|
||||||
|
log.Fatalf("parent type unkown,use -T <tls|tcp|ssh>")
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "ssh" {
|
||||||
|
if *s.cfg.SSHUser == "" {
|
||||||
|
log.Fatalf("ssh user required")
|
||||||
|
}
|
||||||
|
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||||
|
log.Fatalf("ssh password or key required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.cfg.SSHPassword != "" {
|
||||||
|
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
||||||
|
} else {
|
||||||
|
var SSHSigner ssh.Signer
|
||||||
|
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("read key file ERR: %s", err)
|
||||||
|
}
|
||||||
|
if *s.cfg.SSHKeyFileSalt != "" {
|
||||||
|
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||||
|
} else {
|
||||||
|
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parse ssh private key fail,ERR: %s", err)
|
||||||
|
}
|
||||||
|
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *HTTP) InitService() {
|
||||||
|
s.InitBasicAuth()
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
s.checker = utils.NewChecker(*s.cfg.HTTPTimeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "ssh" {
|
||||||
|
err := s.ConnectSSH()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("init service fail, ERR: %s", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
//循环检查ssh网络连通性
|
||||||
|
for {
|
||||||
|
conn, err := utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout*2)
|
||||||
|
if err == nil {
|
||||||
|
_, err = conn.Write([]byte{0})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if s.sshClient != nil {
|
||||||
|
s.sshClient.Close()
|
||||||
|
if s.sshClient.Conn != nil {
|
||||||
|
s.sshClient.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("ssh offline, retrying...")
|
||||||
|
s.ConnectSSH()
|
||||||
|
} else {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *HTTP) StopService() {
|
||||||
|
if s.outPool.Pool != nil {
|
||||||
|
s.outPool.Pool.ReleaseAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *HTTP) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(HTTPArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
s.InitOutConnPool()
|
||||||
|
}
|
||||||
|
s.InitService()
|
||||||
|
for _, addr := range strings.Split(*s.cfg.Local, ",") {
|
||||||
|
if addr != "" {
|
||||||
|
host, port, _ := net.SplitHostPort(addr)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p)
|
||||||
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
|
err = sc.ListenTCP(s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
|
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTP) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var err interface{}
|
||||||
|
var req utils.HTTPRequest
|
||||||
|
req, err = utils.NewHTTPRequest(&inConn, 4096, s.IsBasicAuth(), &s.basicAuth)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Printf("decoder error , from %s, ERR:%s", inConn.RemoteAddr(), err)
|
||||||
|
}
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
address := req.Host
|
||||||
|
host, _, _ := net.SplitHostPort(address)
|
||||||
|
useProxy := false
|
||||||
|
if !utils.IsIternalIP(host) {
|
||||||
|
useProxy = true
|
||||||
|
if *s.cfg.Parent == "" {
|
||||||
|
useProxy = false
|
||||||
|
} else if *s.cfg.Always {
|
||||||
|
useProxy = true
|
||||||
|
} else {
|
||||||
|
s.checker.Add(address)
|
||||||
|
//var n, m uint
|
||||||
|
useProxy, _, _ = s.checker.IsBlocked(req.Host)
|
||||||
|
//log.Printf("blocked ? : %v, %s , fail:%d ,success:%d", useProxy, address, n, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("use proxy : %v, %s", useProxy, address)
|
||||||
|
|
||||||
|
err = s.OutToTCP(useProxy, address, &inConn, &req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if *s.cfg.Parent == "" {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *utils.HTTPRequest) (err interface{}) {
|
||||||
|
inAddr := (*inConn).RemoteAddr().String()
|
||||||
|
inLocalAddr := (*inConn).LocalAddr().String()
|
||||||
|
//防止死循环
|
||||||
|
if s.IsDeadLoop(inLocalAddr, req.Host) {
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
err = fmt.Errorf("dead loop detected , %s", req.Host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var outConn net.Conn
|
||||||
|
var _outConn interface{}
|
||||||
|
tryCount := 0
|
||||||
|
maxTryCount := 5
|
||||||
|
for {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outConn, err = utils.ConnectHost(address, *s.cfg.Timeout)
|
||||||
|
}
|
||||||
|
tryCount++
|
||||||
|
if err == nil || tryCount > maxTryCount {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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需要转发
|
||||||
|
_, err = outConn.Write(req.HeadBuf)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTP) getSSHConn(host string) (outConn net.Conn, err interface{}) {
|
||||||
|
maxTryCount := 1
|
||||||
|
tryCount := 0
|
||||||
|
RETRY:
|
||||||
|
if tryCount >= maxTryCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wait := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
err = recover()
|
||||||
|
}
|
||||||
|
wait <- true
|
||||||
|
}()
|
||||||
|
outConn, err = s.sshClient.Dial("tcp", host)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-wait:
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
err = fmt.Errorf("ssh dial %s timeout", host)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||||
|
e := s.ConnectSSH()
|
||||||
|
if e == nil {
|
||||||
|
tryCount++
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
goto RETRY
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *HTTP) ConnectSSH() (err error) {
|
||||||
|
select {
|
||||||
|
case s.lockChn <- true:
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("can not connect at same time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
Timeout: time.Duration(*s.cfg.Timeout) * time.Millisecond,
|
||||||
|
User: *s.cfg.SSHUser,
|
||||||
|
Auth: []ssh.AuthMethod{s.cfg.SSHAuthMethod},
|
||||||
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if s.sshClient != nil {
|
||||||
|
s.sshClient.Close()
|
||||||
|
}
|
||||||
|
s.sshClient, err = ssh.Dial("tcp", *s.cfg.Parent, &config)
|
||||||
|
<-s.lockChn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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.cfg.CheckParentInterval,
|
||||||
|
*s.cfg.ParentType,
|
||||||
|
*s.cfg.KCPMethod,
|
||||||
|
*s.cfg.KCPKey,
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes,
|
||||||
|
*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.AuthURL != "" {
|
||||||
|
s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *HTTP) IsBasicAuth() bool {
|
||||||
|
return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != ""
|
||||||
|
}
|
||||||
|
func (s *HTTP) IsDeadLoop(inLocalAddr string, host string) bool {
|
||||||
|
inIP, inPort, err := net.SplitHostPort(inLocalAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
outDomain, outPort, err := net.SplitHostPort(host)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if inPort == outPort {
|
||||||
|
var outIPs []net.IP
|
||||||
|
outIPs, err = net.LookupIP(outDomain)
|
||||||
|
if err == nil {
|
||||||
|
for _, ip := range outIPs {
|
||||||
|
if ip.String() == inIP {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interfaceIPs, err := utils.GetAllInterfaceAddr()
|
||||||
|
for _, ip := range *s.cfg.LocalIPS {
|
||||||
|
interfaceIPs = append(interfaceIPs, net.ParseIP(ip).To4())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, localIP := range interfaceIPs {
|
||||||
|
for _, outIP := range outIPs {
|
||||||
|
if localIP.Equal(outIP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
155
services/mux_bridge.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtaci/smux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxBridge struct {
|
||||||
|
cfg MuxBridgeArgs
|
||||||
|
clientControlConns utils.ConcurrentMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxBridge() Service {
|
||||||
|
return &MuxBridge{
|
||||||
|
cfg: MuxBridgeArgs{},
|
||||||
|
clientControlConns: utils.NewConcurrentMap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxBridge) InitService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) CheckArgs() {
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
log.Fatalf("cert and key file required")
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) StopService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(MuxBridgeArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
s.InitService()
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p)
|
||||||
|
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, func(inConn net.Conn) {
|
||||||
|
reader := bufio.NewReader(inConn)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var connType uint8
|
||||||
|
var key string
|
||||||
|
err = utils.ReadPacket(reader, &connType, &key)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch connType {
|
||||||
|
case CONN_SERVER:
|
||||||
|
var serverID string
|
||||||
|
err = utils.ReadPacketData(reader, &serverID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("server connection %s %s connected", serverID, key)
|
||||||
|
session, err := smux.Server(inConn, nil)
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
log.Printf("server session error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
stream, err := session.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go s.callback(stream, serverID, key)
|
||||||
|
}
|
||||||
|
case CONN_CLIENT:
|
||||||
|
|
||||||
|
log.Printf("client connection %s connected", key)
|
||||||
|
session, err := smux.Client(inConn, nil)
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
log.Printf("client session error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.clientControlConns.Set(key, session)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if session.IsClosed() {
|
||||||
|
s.clientControlConns.Remove(key)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
//log.Printf("set client session,key: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("proxy on mux bridge mode %s", (*sc.Listener).Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) {
|
||||||
|
try := 20
|
||||||
|
for {
|
||||||
|
try--
|
||||||
|
if try == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
session, ok := s.clientControlConns.Get(key)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("client %s session not exists for server stream %s", key, serverID)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stream, err := session.(*smux.Session).OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s client session open stream %s fail, err: %s, retrying...", key, serverID, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Printf("%s server %s stream created", key, serverID)
|
||||||
|
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()
|
||||||
|
log.Printf("%s server %s stream released", key, serverID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
206
services/mux_client.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/snappy"
|
||||||
|
"github.com/xtaci/smux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxClient struct {
|
||||||
|
cfg MuxClientArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxClient() Service {
|
||||||
|
return &MuxClient{
|
||||||
|
cfg: MuxClientArgs{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxClient) InitService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxClient) CheckArgs() {
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("parent required")
|
||||||
|
}
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
log.Fatalf("cert and key file required")
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
func (s *MuxClient) StopService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *MuxClient) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(MuxClientArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
s.InitService()
|
||||||
|
log.Printf("proxy on mux client mode, compress %v", *s.cfg.IsCompress)
|
||||||
|
for {
|
||||||
|
var _conn tls.Conn
|
||||||
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("connection err: %s, retrying...", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn := net.Conn(&_conn)
|
||||||
|
_, err = conn.Write(utils.BuildPacket(CONN_CLIENT, *s.cfg.Key))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
log.Printf("connection err: %s, retrying...", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
session, err := smux.Server(conn, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("session err: %s, retrying...", err)
|
||||||
|
conn.Close()
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
stream, err := session.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("accept stream err: %s, retrying...", err)
|
||||||
|
session.Close()
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
var ID, clientLocalAddr, serverID string
|
||||||
|
err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read stream signal err: %s", err)
|
||||||
|
stream.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("signal revecived,server %s stream %s %s", serverID, ID, clientLocalAddr)
|
||||||
|
protocol := clientLocalAddr[:3]
|
||||||
|
localAddr := clientLocalAddr[4:]
|
||||||
|
if protocol == "udp" {
|
||||||
|
s.ServeUDP(stream, localAddr, ID)
|
||||||
|
} else {
|
||||||
|
s.ServeConn(stream, localAddr, ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *MuxClient) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) {
|
||||||
|
|
||||||
|
for {
|
||||||
|
srcAddr, body, err := utils.ReadUDPPacket(inConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("udp packet revecived fail, err: %s", err)
|
||||||
|
log.Printf("connection %s released", ID)
|
||||||
|
inConn.Close()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
//log.Printf("udp packet revecived:%s,%v", srcAddr, body)
|
||||||
|
go 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 {
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBody := buf[0:length]
|
||||||
|
//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)
|
||||||
|
inConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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 {
|
||||||
|
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)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
inConn.Close()
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
log.Printf("build connection error, err: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||||
|
} else {
|
||||||
|
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||||
|
log.Printf("stream %s released", ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
333
services/mux_server.go
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/snappy"
|
||||||
|
"github.com/xtaci/smux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MuxServer struct {
|
||||||
|
cfg MuxServerArgs
|
||||||
|
udpChn chan MuxUDPItem
|
||||||
|
sc utils.ServerChannel
|
||||||
|
session *smux.Session
|
||||||
|
lockChn chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type MuxServerManager struct {
|
||||||
|
cfg MuxServerArgs
|
||||||
|
udpChn chan MuxUDPItem
|
||||||
|
sc utils.ServerChannel
|
||||||
|
serverID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxServerManager() Service {
|
||||||
|
return &MuxServerManager{
|
||||||
|
cfg: MuxServerArgs{},
|
||||||
|
udpChn: make(chan MuxUDPItem, 50000),
|
||||||
|
serverID: utils.Uniqueid(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(MuxServerArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("parent required")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.InitService()
|
||||||
|
|
||||||
|
log.Printf("server id: %s", s.serverID)
|
||||||
|
//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,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) StopService() {
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) CheckArgs() {
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
log.Fatalf("cert and key file required")
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
func (s *MuxServerManager) InitService() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMuxServer() Service {
|
||||||
|
return &MuxServer{
|
||||||
|
cfg: MuxServerArgs{},
|
||||||
|
udpChn: make(chan MuxUDPItem, 50000),
|
||||||
|
lockChn: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MuxUDPItem struct {
|
||||||
|
packet *[]byte
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
srcAddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxServer) InitService() {
|
||||||
|
s.UDPConnDeamon()
|
||||||
|
}
|
||||||
|
func (s *MuxServer) CheckArgs() {
|
||||||
|
if *s.cfg.Remote == "" {
|
||||||
|
log.Fatalf("remote required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MuxServer) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(MuxServerArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
s.InitService()
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
s.sc = utils.NewServerChannel(host, p)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
log.Printf("proxy on udp mux 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("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var outConn net.Conn
|
||||||
|
var ID string
|
||||||
|
for {
|
||||||
|
outConn, ID, err = s.GetOutConn()
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
log.Printf("connect to %s fail, err: %s, retrying...", *s.cfg.Parent, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||||
|
} else {
|
||||||
|
utils.IoBind(inConn, outConn, func(err interface{}) {
|
||||||
|
log.Printf("%s stream %s released", *s.cfg.Key, ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("proxy on mux server mode %s, compress %v", (*s.sc.Listener).Addr(), *s.cfg.IsCompress)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) Clean() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) {
|
||||||
|
outConn, err = s.GetConn()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("connection err: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remoteAddr := "tcp:" + *s.cfg.Remote
|
||||||
|
if *s.cfg.IsUDP {
|
||||||
|
remoteAddr = "udp:" + *s.cfg.Remote
|
||||||
|
}
|
||||||
|
ID = utils.Uniqueid()
|
||||||
|
_, err = outConn.Write(utils.BuildPacketData(ID, remoteAddr, s.cfg.Mgr.serverID))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("write stream data err: %s ,retrying...", err)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) GetConn() (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
|
||||||
|
}()
|
||||||
|
if s.session == nil {
|
||||||
|
var _conn tls.Conn
|
||||||
|
_conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
s.session = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := net.Conn(&_conn)
|
||||||
|
_, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID))
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
s.session = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
s.session, err = smux.Client(c, nil)
|
||||||
|
if err != nil {
|
||||||
|
s.session = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = s.session.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
s.session.Close()
|
||||||
|
s.session = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MuxServer) 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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var outConn net.Conn
|
||||||
|
var ID string
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
item := <-s.udpChn
|
||||||
|
RETRY:
|
||||||
|
if outConn == nil {
|
||||||
|
for {
|
||||||
|
outConn, ID, err = s.GetOutConn()
|
||||||
|
if err != nil {
|
||||||
|
outConn = nil
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
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() {
|
||||||
|
// outConn.Close()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body)
|
||||||
|
log.Printf("UDP deamon connection %s exited", ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
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)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//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
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
52
services/service.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Start(args interface{}) (err error)
|
||||||
|
Clean()
|
||||||
|
}
|
||||||
|
type ServiceItem struct {
|
||||||
|
S Service
|
||||||
|
Args interface{}
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var servicesMap = map[string]*ServiceItem{}
|
||||||
|
|
||||||
|
func Regist(name string, s Service, args interface{}) {
|
||||||
|
servicesMap[name] = &ServiceItem{
|
||||||
|
S: s,
|
||||||
|
Args: args,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if len(args) == 1 {
|
||||||
|
err = service.S.Start(args[0])
|
||||||
|
} else {
|
||||||
|
err = service.S.Start(service.Args)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s servcie fail, ERR: %s", name, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("service %s not found", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
627
services/socks.go
Normal file
@ -0,0 +1,627 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"proxy/utils/aes"
|
||||||
|
"proxy/utils/socks"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Socks struct {
|
||||||
|
cfg SocksArgs
|
||||||
|
checker utils.Checker
|
||||||
|
basicAuth utils.BasicAuth
|
||||||
|
sshClient *ssh.Client
|
||||||
|
lockChn chan bool
|
||||||
|
udpSC utils.ServerChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSocks() Service {
|
||||||
|
return &Socks{
|
||||||
|
cfg: SocksArgs{},
|
||||||
|
checker: utils.Checker{},
|
||||||
|
basicAuth: utils.BasicAuth{},
|
||||||
|
lockChn: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
if *s.cfg.ParentType == "" {
|
||||||
|
log.Fatalf("parent type unkown,use -T <tls|tcp|ssh>")
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "tls" {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "ssh" {
|
||||||
|
if *s.cfg.SSHUser == "" {
|
||||||
|
log.Fatalf("ssh user required")
|
||||||
|
}
|
||||||
|
if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" {
|
||||||
|
log.Fatalf("ssh password or key required")
|
||||||
|
}
|
||||||
|
if *s.cfg.SSHPassword != "" {
|
||||||
|
s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword)
|
||||||
|
} else {
|
||||||
|
var SSHSigner ssh.Signer
|
||||||
|
s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("read key file ERR: %s", err)
|
||||||
|
}
|
||||||
|
if *s.cfg.SSHKeyFileSalt != "" {
|
||||||
|
SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt))
|
||||||
|
} else {
|
||||||
|
SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parse ssh private key fail,ERR: %s", err)
|
||||||
|
}
|
||||||
|
s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *Socks) InitService() {
|
||||||
|
s.InitBasicAuth()
|
||||||
|
s.checker = utils.NewChecker(*s.cfg.Timeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct)
|
||||||
|
if *s.cfg.ParentType == "ssh" {
|
||||||
|
err := s.ConnectSSH()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("init service fail, ERR: %s", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
//循环检查ssh网络连通性
|
||||||
|
for {
|
||||||
|
conn, err := utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout*2)
|
||||||
|
if err == nil {
|
||||||
|
_, err = conn.Write([]byte{0})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if s.sshClient != nil {
|
||||||
|
s.sshClient.Close()
|
||||||
|
}
|
||||||
|
log.Printf("ssh offline, retrying...")
|
||||||
|
s.ConnectSSH()
|
||||||
|
} else {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "ssh" {
|
||||||
|
log.Println("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)
|
||||||
|
}
|
||||||
|
log.Printf("udp socks proxy on %s", s.udpSC.UDPListener.LocalAddr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *Socks) StopService() {
|
||||||
|
if s.sshClient != nil {
|
||||||
|
s.sshClient.Close()
|
||||||
|
}
|
||||||
|
if s.udpSC.UDPListener != nil {
|
||||||
|
s.udpSC.UDPListener.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *Socks) Start(args interface{}) (err error) {
|
||||||
|
//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)
|
||||||
|
}
|
||||||
|
sc := utils.NewServerChannelHost(*s.cfg.Local)
|
||||||
|
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)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
|
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.socksConnCallback)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("%s socks proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Socks) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *Socks) UDPKey() []byte {
|
||||||
|
return s.cfg.KeyBytes[:32]
|
||||||
|
}
|
||||||
|
func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||||
|
rawB := b
|
||||||
|
var err error
|
||||||
|
if *s.cfg.LocalType == "tls" {
|
||||||
|
//decode b
|
||||||
|
rawB, err = goaes.Decrypt(s.UDPKey(), b)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("decrypt udp packet fail from %s", srcAddr.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p, err := socks.ParseUDPPacket(rawB)
|
||||||
|
log.Printf("udp revecived:%v", len(p.Data()))
|
||||||
|
if err != nil {
|
||||||
|
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())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent := *s.cfg.UDPParent
|
||||||
|
if parent == "" {
|
||||||
|
parent = *s.cfg.Parent
|
||||||
|
}
|
||||||
|
dstAddr, err := net.ResolveUDPAddr("udp", parent)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
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))
|
||||||
|
if err != nil {
|
||||||
|
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())
|
||||||
|
buf := make([]byte, 10*1024)
|
||||||
|
length, _, err := conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
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())
|
||||||
|
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)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
||||||
|
log.Printf("udp reply:%v", len(d))
|
||||||
|
} else {
|
||||||
|
s.udpSC.UDPListener.WriteToUDP(respBody, srcAddr)
|
||||||
|
log.Printf("udp reply:%v", len(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//本地代理
|
||||||
|
dstAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(p.Host(), p.Port()))
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
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()))
|
||||||
|
if err != nil {
|
||||||
|
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())
|
||||||
|
buf := make([]byte, 10*1024)
|
||||||
|
length, _, err := conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
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())
|
||||||
|
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())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.udpSC.UDPListener.WriteToUDP(d, srcAddr)
|
||||||
|
} else {
|
||||||
|
s.udpSC.UDPListener.WriteToUDP(respPacket, srcAddr)
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
inConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
//协商开始
|
||||||
|
|
||||||
|
//method select request
|
||||||
|
inConn.SetReadDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
methodReq, err := socks.NewMethodsRequest(inConn)
|
||||||
|
inConn.SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
log.Printf("new methods request fail,ERR: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.IsBasicAuth() {
|
||||||
|
if !methodReq.Select(socks.Method_NO_AUTH) {
|
||||||
|
methodReq.Reply(socks.Method_NONE_ACCEPTABLE)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
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)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//method reply need auth
|
||||||
|
err = methodReq.Reply(socks.Method_USER_PASS)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("reply answer data fail,ERR: %s", err)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//read auth
|
||||||
|
buf := make([]byte, 500)
|
||||||
|
inConn.SetReadDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
n, err := inConn.Read(buf)
|
||||||
|
inConn.SetReadDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
//auth
|
||||||
|
_addr := strings.Split(inConn.RemoteAddr().String(), ":")
|
||||||
|
if s.basicAuth.CheckUserPass(user, pass, _addr[0], "") {
|
||||||
|
inConn.Write([]byte{0x01, 0x00})
|
||||||
|
} else {
|
||||||
|
inConn.Write([]byte{0x01, 0x01})
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//request detail
|
||||||
|
request, err := socks.NewRequest(inConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read request data fail,ERR: %s", err)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//协商结束
|
||||||
|
|
||||||
|
switch request.CMD() {
|
||||||
|
case socks.CMD_BIND:
|
||||||
|
//bind 不支持
|
||||||
|
request.TCPReply(socks.REP_UNKNOWN)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
return
|
||||||
|
case socks.CMD_CONNECT:
|
||||||
|
//tcp
|
||||||
|
s.proxyTCP(&inConn, methodReq, request)
|
||||||
|
case socks.CMD_ASSOCIATE:
|
||||||
|
//udp
|
||||||
|
s.proxyUDP(&inConn, methodReq, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *Socks) proxyUDP(inConn *net.Conn, methodReq socks.MethodsRequest, request socks.Request) {
|
||||||
|
if *s.cfg.ParentType == "ssh" {
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
host, _, _ := net.SplitHostPort((*inConn).LocalAddr().String())
|
||||||
|
_, port, _ := net.SplitHostPort(s.udpSC.UDPListener.LocalAddr().String())
|
||||||
|
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) {
|
||||||
|
var outConn net.Conn
|
||||||
|
var err interface{}
|
||||||
|
useProxy := true
|
||||||
|
tryCount := 0
|
||||||
|
maxTryCount := 5
|
||||||
|
//防止死循环
|
||||||
|
if s.IsDeadLoop((*inConn).LocalAddr().String(), request.Host()) {
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
log.Printf("dead loop detected , %s", request.Host())
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if *s.cfg.Always {
|
||||||
|
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
||||||
|
} else {
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
host, _, _ := net.SplitHostPort(request.Addr())
|
||||||
|
useProxy := false
|
||||||
|
if utils.IsIternalIP(host) {
|
||||||
|
useProxy = false
|
||||||
|
} else {
|
||||||
|
s.checker.Add(request.Addr())
|
||||||
|
useProxy, _, _ = s.checker.IsBlocked(request.Addr())
|
||||||
|
}
|
||||||
|
if useProxy {
|
||||||
|
outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr())
|
||||||
|
} else {
|
||||||
|
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outConn, err = utils.ConnectHost(request.Addr(), *s.cfg.Timeout)
|
||||||
|
useProxy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tryCount++
|
||||||
|
if err == nil || tryCount > maxTryCount || *s.cfg.Parent == "" {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
request.TCPReply(socks.REP_NETWOR_UNREACHABLE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
utils.IoBind(*inConn, outConn, func(err interface{}) {
|
||||||
|
log.Printf("conn %s - %s released", inAddr, request.Addr())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn net.Conn, err interface{}) {
|
||||||
|
switch *s.cfg.ParentType {
|
||||||
|
case "kcp":
|
||||||
|
fallthrough
|
||||||
|
case "tls":
|
||||||
|
fallthrough
|
||||||
|
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 = net.Conn(&_outConn)
|
||||||
|
} else if *s.cfg.ParentType == "kcp" {
|
||||||
|
outConn, err = utils.ConnectKCPHost(*s.cfg.Parent, *s.cfg.KCPMethod, *s.cfg.KCPKey)
|
||||||
|
} else {
|
||||||
|
outConn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("connect fail,%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var buf = make([]byte, 1024)
|
||||||
|
//var n int
|
||||||
|
_, err = outConn.Write(methodBytes)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("write method fail,%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = outConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read method reply fail,%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//resp := buf[:n]
|
||||||
|
//log.Printf("resp:%v", resp)
|
||||||
|
|
||||||
|
_, err = outConn.Write(reqBytes)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("write req detail fail,%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = outConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("read req reply fail,%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//result := buf[:n]
|
||||||
|
//log.Printf("result:%v", result)
|
||||||
|
|
||||||
|
case "ssh":
|
||||||
|
maxTryCount := 1
|
||||||
|
tryCount := 0
|
||||||
|
RETRY:
|
||||||
|
if tryCount >= maxTryCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wait := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
err = recover()
|
||||||
|
}
|
||||||
|
wait <- true
|
||||||
|
}()
|
||||||
|
outConn, err = s.sshClient.Dial("tcp", host)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-wait:
|
||||||
|
case <-time.After(time.Millisecond * time.Duration(*s.cfg.Timeout) * 2):
|
||||||
|
err = fmt.Errorf("ssh dial %s timeout", host)
|
||||||
|
s.sshClient.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("connect ssh fail, ERR: %s, retrying...", err)
|
||||||
|
e := s.ConnectSSH()
|
||||||
|
if e == nil {
|
||||||
|
tryCount++
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
goto RETRY
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Socks) ConnectSSH() (err error) {
|
||||||
|
select {
|
||||||
|
case s.lockChn <- true:
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("can not connect at same time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
Timeout: time.Duration(*s.cfg.Timeout) * time.Millisecond,
|
||||||
|
User: *s.cfg.SSHUser,
|
||||||
|
Auth: []ssh.AuthMethod{s.cfg.SSHAuthMethod},
|
||||||
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if s.sshClient != nil {
|
||||||
|
s.sshClient.Close()
|
||||||
|
}
|
||||||
|
s.sshClient, err = ssh.Dial("tcp", *s.cfg.Parent, &config)
|
||||||
|
<-s.lockChn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Socks) InitBasicAuth() (err error) {
|
||||||
|
s.basicAuth = utils.NewBasicAuth()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Socks) IsBasicAuth() bool {
|
||||||
|
return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != ""
|
||||||
|
}
|
||||||
|
func (s *Socks) IsDeadLoop(inLocalAddr string, host string) bool {
|
||||||
|
inIP, inPort, err := net.SplitHostPort(inLocalAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
outDomain, outPort, err := net.SplitHostPort(host)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if inPort == outPort {
|
||||||
|
var outIPs []net.IP
|
||||||
|
outIPs, err = net.LookupIP(outDomain)
|
||||||
|
if err == nil {
|
||||||
|
for _, ip := range outIPs {
|
||||||
|
if ip.String() == inIP {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interfaceIPs, err := utils.GetAllInterfaceAddr()
|
||||||
|
for _, ip := range *s.cfg.LocalIPS {
|
||||||
|
interfaceIPs = append(interfaceIPs, net.ParseIP(ip).To4())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, localIP := range interfaceIPs {
|
||||||
|
for _, outIP := range outIPs {
|
||||||
|
if localIP.Equal(outIP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
183
services/tcp.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCP struct {
|
||||||
|
outPool utils.OutPool
|
||||||
|
cfg TCPArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTCP() Service {
|
||||||
|
return &TCP{
|
||||||
|
outPool: utils.OutPool{},
|
||||||
|
cfg: TCPArgs{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *TCP) CheckArgs() {
|
||||||
|
if *s.cfg.Parent == "" {
|
||||||
|
log.Fatalf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "" {
|
||||||
|
log.Fatalf("parent type unkown,use -T <tls|tcp|kcp|udp>")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *TCP) InitService() {
|
||||||
|
s.InitOutConnPool()
|
||||||
|
}
|
||||||
|
func (s *TCP) StopService() {
|
||||||
|
if s.outPool.Pool != nil {
|
||||||
|
s.outPool.Pool.ReleaseAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *TCP) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(TCPArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
s.InitService()
|
||||||
|
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p)
|
||||||
|
|
||||||
|
if *s.cfg.LocalType == TYPE_TCP {
|
||||||
|
err = sc.ListenTCP(s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_TLS {
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback)
|
||||||
|
} else if *s.cfg.LocalType == TYPE_KCP {
|
||||||
|
err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("%s proxy on %s", s.cfg.Protocol(), (*sc.Listener).Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TCP) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
switch *s.cfg.ParentType {
|
||||||
|
case TYPE_KCP:
|
||||||
|
fallthrough
|
||||||
|
case TYPE_TCP:
|
||||||
|
fallthrough
|
||||||
|
case TYPE_TLS:
|
||||||
|
err = s.OutToTCP(&inConn)
|
||||||
|
case TYPE_UDP:
|
||||||
|
err = s.OutToUDP(&inConn)
|
||||||
|
default:
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("connect to %s , err:%s", *s.cfg.Parent, err)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inAddr := (*inConn).RemoteAddr().String()
|
||||||
|
//inLocalAddr := (*inConn).LocalAddr().String()
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *TCP) OutToUDP(inConn *net.Conn) (err error) {
|
||||||
|
log.Printf("conn created , remote : %s ", (*inConn).RemoteAddr())
|
||||||
|
for {
|
||||||
|
srcAddr, body, err := utils.ReadUDPPacket(bufio.NewReader(*inConn))
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
//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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//log.Printf("send udp response success ,from:%s", dstAddr.String())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
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.cfg.CheckParentInterval,
|
||||||
|
*s.cfg.ParentType,
|
||||||
|
*s.cfg.KCPMethod,
|
||||||
|
*s.cfg.KCPKey,
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes,
|
||||||
|
*s.cfg.Parent,
|
||||||
|
*s.cfg.Timeout,
|
||||||
|
*s.cfg.PoolSize,
|
||||||
|
*s.cfg.PoolSize*2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
244
services/tunnel_bridge.go
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
//ClientLocalAddr string //tcp:2.2.22:333@ID
|
||||||
|
Conn *net.Conn
|
||||||
|
}
|
||||||
|
type TunnelBridge struct {
|
||||||
|
cfg TunnelBridgeArgs
|
||||||
|
serverConns utils.ConcurrentMap
|
||||||
|
clientControlConns utils.ConcurrentMap
|
||||||
|
// cmServer utils.ConnManager
|
||||||
|
// cmClient utils.ConnManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTunnelBridge() Service {
|
||||||
|
return &TunnelBridge{
|
||||||
|
cfg: TunnelBridgeArgs{},
|
||||||
|
serverConns: utils.NewConcurrentMap(),
|
||||||
|
clientControlConns: utils.NewConcurrentMap(),
|
||||||
|
// cmServer: utils.NewConnManager(),
|
||||||
|
// cmClient: utils.NewConnManager(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TunnelBridge) InitService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *TunnelBridge) CheckArgs() {
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
log.Fatalf("cert and key file required")
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
func (s *TunnelBridge) StopService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *TunnelBridge) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(TunnelBridgeArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
s.InitService()
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p)
|
||||||
|
|
||||||
|
err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, func(inConn net.Conn) {
|
||||||
|
//log.Printf("connection from %s ", inConn.RemoteAddr())
|
||||||
|
|
||||||
|
reader := bufio.NewReader(inConn)
|
||||||
|
var err error
|
||||||
|
var connType uint8
|
||||||
|
err = utils.ReadPacket(reader, &connType)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch connType {
|
||||||
|
case CONN_SERVER:
|
||||||
|
var key, ID, clientLocalAddr, serverID string
|
||||||
|
err = utils.ReadPacketData(reader, &key, &ID, &clientLocalAddr, &serverID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet := utils.BuildPacketData(ID, clientLocalAddr, serverID)
|
||||||
|
log.Printf("server connection, key: %s , id: %s %s %s", key, ID, clientLocalAddr, serverID)
|
||||||
|
|
||||||
|
//addr := clientLocalAddr + "@" + ID
|
||||||
|
s.serverConns.Set(ID, ServerConn{
|
||||||
|
Conn: &inConn,
|
||||||
|
})
|
||||||
|
for {
|
||||||
|
item, ok := s.clientControlConns.Get(key)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("client %s control conn not exists", key)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
(*item.(*net.Conn)).SetWriteDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
_, err := (*item.(*net.Conn)).Write(packet)
|
||||||
|
(*item.(*net.Conn)).SetWriteDeadline(time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s client control conn write signal fail, err: %s, retrying...", key, err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// s.cmServer.Add(serverID, ID, &inConn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case CONN_CLIENT:
|
||||||
|
var key, ID, serverID string
|
||||||
|
err = utils.ReadPacketData(reader, &key, &ID, &serverID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("client connection , key: %s , id: %s, server id:%s", key, ID, serverID)
|
||||||
|
|
||||||
|
serverConnItem, ok := s.serverConns.Get(ID)
|
||||||
|
if !ok {
|
||||||
|
inConn.Close()
|
||||||
|
log.Printf("server conn %s exists", ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverConn := serverConnItem.(ServerConn).Conn
|
||||||
|
utils.IoBind(*serverConn, inConn, func(err interface{}) {
|
||||||
|
s.serverConns.Remove(ID)
|
||||||
|
// s.cmClient.RemoveOne(key, ID)
|
||||||
|
// s.cmServer.RemoveOne(serverID, ID)
|
||||||
|
log.Printf("conn %s released", ID)
|
||||||
|
})
|
||||||
|
// s.cmClient.Add(key, ID, &inConn)
|
||||||
|
log.Printf("conn %s created", ID)
|
||||||
|
|
||||||
|
case CONN_CLIENT_CONTROL:
|
||||||
|
var key string
|
||||||
|
err = utils.ReadPacketData(reader, &key)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error,ERR:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("client control connection, key: %s", key)
|
||||||
|
if s.clientControlConns.Has(key) {
|
||||||
|
item, _ := s.clientControlConns.Get(key)
|
||||||
|
(*item.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
s.clientControlConns.Set(key, &inConn)
|
||||||
|
log.Printf("set client %s control conn", key)
|
||||||
|
|
||||||
|
// case CONN_SERVER_HEARBEAT:
|
||||||
|
// var serverID string
|
||||||
|
// err = utils.ReadPacketData(reader, &serverID)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("read error,ERR:%s", err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// log.Printf("server heartbeat connection, id: %s", serverID)
|
||||||
|
// writeDie := make(chan bool)
|
||||||
|
// readDie := make(chan bool)
|
||||||
|
// go func() {
|
||||||
|
// for {
|
||||||
|
// inConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
// _, err = inConn.Write([]byte{0x00})
|
||||||
|
// inConn.SetWriteDeadline(time.Time{})
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("server heartbeat connection write err %s", err)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// time.Sleep(time.Second * 3)
|
||||||
|
// }
|
||||||
|
// close(writeDie)
|
||||||
|
// }()
|
||||||
|
// go func() {
|
||||||
|
// for {
|
||||||
|
// signal := make([]byte, 1)
|
||||||
|
// inConn.SetReadDeadline(time.Now().Add(time.Second * 6))
|
||||||
|
// _, err := inConn.Read(signal)
|
||||||
|
// inConn.SetReadDeadline(time.Time{})
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("server heartbeat connection read err: %s", err)
|
||||||
|
// break
|
||||||
|
// } else {
|
||||||
|
// // log.Printf("heartbeat from server ,id:%s", serverID)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// close(readDie)
|
||||||
|
// }()
|
||||||
|
// select {
|
||||||
|
// case <-readDie:
|
||||||
|
// case <-writeDie:
|
||||||
|
// }
|
||||||
|
// utils.CloseConn(&inConn)
|
||||||
|
// s.cmServer.Remove(serverID)
|
||||||
|
// log.Printf("server heartbeat conn %s released", serverID)
|
||||||
|
// case CONN_CLIENT_HEARBEAT:
|
||||||
|
// var clientID string
|
||||||
|
// err = utils.ReadPacketData(reader, &clientID)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("read error,ERR:%s", err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// log.Printf("client heartbeat connection, id: %s", clientID)
|
||||||
|
// writeDie := make(chan bool)
|
||||||
|
// readDie := make(chan bool)
|
||||||
|
// go func() {
|
||||||
|
// for {
|
||||||
|
// inConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
||||||
|
// _, err = inConn.Write([]byte{0x00})
|
||||||
|
// inConn.SetWriteDeadline(time.Time{})
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("client heartbeat connection write err %s", err)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// time.Sleep(time.Second * 3)
|
||||||
|
// }
|
||||||
|
// close(writeDie)
|
||||||
|
// }()
|
||||||
|
// go func() {
|
||||||
|
// for {
|
||||||
|
// signal := make([]byte, 1)
|
||||||
|
// inConn.SetReadDeadline(time.Now().Add(time.Second * 6))
|
||||||
|
// _, err := inConn.Read(signal)
|
||||||
|
// inConn.SetReadDeadline(time.Time{})
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("client control connection read err: %s", err)
|
||||||
|
// break
|
||||||
|
// } else {
|
||||||
|
// // log.Printf("heartbeat from client ,id:%s", clientID)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// close(readDie)
|
||||||
|
// }()
|
||||||
|
// select {
|
||||||
|
// case <-readDie:
|
||||||
|
// case <-writeDie:
|
||||||
|
// }
|
||||||
|
// utils.CloseConn(&inConn)
|
||||||
|
// s.cmClient.Remove(clientID)
|
||||||
|
// if s.clientControlConns.Has(clientID) {
|
||||||
|
// item, _ := s.clientControlConns.Get(clientID)
|
||||||
|
// (*item.(*net.Conn)).Close()
|
||||||
|
// }
|
||||||
|
// s.clientControlConns.Remove(clientID)
|
||||||
|
// log.Printf("client heartbeat conn %s released", clientID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("proxy on tunnel bridge mode %s", (*sc.Listener).Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *TunnelBridge) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
283
services/tunnel_client.go
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TunnelClient struct {
|
||||||
|
cfg TunnelClientArgs
|
||||||
|
// cm utils.ConnManager
|
||||||
|
ctrlConn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTunnelClient() Service {
|
||||||
|
return &TunnelClient{
|
||||||
|
cfg: TunnelClientArgs{},
|
||||||
|
// cm: utils.NewConnManager(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TunnelClient) InitService() {
|
||||||
|
// s.InitHeartbeatDeamon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("parent required")
|
||||||
|
}
|
||||||
|
if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" {
|
||||||
|
log.Fatalf("cert and key file required")
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
func (s *TunnelClient) StopService() {
|
||||||
|
// s.cm.RemoveAll()
|
||||||
|
}
|
||||||
|
func (s *TunnelClient) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(TunnelClientArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
s.InitService()
|
||||||
|
log.Printf("proxy on tunnel client mode")
|
||||||
|
|
||||||
|
for {
|
||||||
|
//close all conn
|
||||||
|
// s.cm.Remove(*s.cfg.Key)
|
||||||
|
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)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
if s.ctrlConn != nil {
|
||||||
|
s.ctrlConn.Close()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
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)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("signal revecived:%s %s %s", serverID, ID, clientLocalAddr)
|
||||||
|
protocol := clientLocalAddr[:3]
|
||||||
|
localAddr := clientLocalAddr[4:]
|
||||||
|
if protocol == "udp" {
|
||||||
|
go s.ServeUDP(localAddr, ID, serverID)
|
||||||
|
} else {
|
||||||
|
go s.ServeConn(localAddr, ID, serverID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *TunnelClient) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
func (s *TunnelClient) GetInConn(typ uint8, data ...string) (outConn net.Conn, err error) {
|
||||||
|
outConn, err = s.GetConn()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("connection err: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = outConn.Write(utils.BuildPacket(typ, data...))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("write connection data err: %s ,retrying...", err)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err == nil {
|
||||||
|
conn = net.Conn(&_conn)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) {
|
||||||
|
var inConn net.Conn
|
||||||
|
var err error
|
||||||
|
// for {
|
||||||
|
for {
|
||||||
|
// 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)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// s.cm.Add(*s.cfg.Key, ID, &inConn)
|
||||||
|
log.Printf("conn %s created", ID)
|
||||||
|
|
||||||
|
for {
|
||||||
|
srcAddr, body, err := utils.ReadUDPPacket(inConn)
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
log.Printf("connection %s released", ID)
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
log.Printf("udp packet revecived fail, err: %s", err)
|
||||||
|
} else {
|
||||||
|
//log.Printf("udp packet revecived:%s,%v", srcAddr, body)
|
||||||
|
go s.processUDPPacket(&inConn, srcAddr, localAddr, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBody := buf[0:length]
|
||||||
|
//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)
|
||||||
|
utils.CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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 {
|
||||||
|
inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID)
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
log.Printf("connection err: %s, retrying...", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
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)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
utils.CloseConn(&inConn)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
log.Printf("build connection error, err: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
362
services/tunnel_server.go
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TunnelServer struct {
|
||||||
|
cfg TunnelServerArgs
|
||||||
|
udpChn chan UDPItem
|
||||||
|
sc utils.ServerChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelServerManager struct {
|
||||||
|
cfg TunnelServerArgs
|
||||||
|
udpChn chan UDPItem
|
||||||
|
sc utils.ServerChannel
|
||||||
|
serverID string
|
||||||
|
// cm utils.ConnManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTunnelServerManager() Service {
|
||||||
|
return &TunnelServerManager{
|
||||||
|
cfg: TunnelServerArgs{},
|
||||||
|
udpChn: make(chan UDPItem, 50000),
|
||||||
|
serverID: utils.Uniqueid(),
|
||||||
|
// cm: utils.NewConnManager(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *TunnelServerManager) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(TunnelServerArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
if *s.cfg.Parent != "" {
|
||||||
|
log.Printf("use tls parent %s", *s.cfg.Parent)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("parent required")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.InitService()
|
||||||
|
|
||||||
|
log.Printf("server id: %s", s.serverID)
|
||||||
|
//log.Printf("route:%v", *s.cfg.Route)
|
||||||
|
for _, _info := range *s.cfg.Route {
|
||||||
|
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 := NewTunnelServer()
|
||||||
|
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(TunnelServerArgs{
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
func (s *TunnelServerManager) InitService() {
|
||||||
|
// s.InitHeartbeatDeamon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ID = s.serverID
|
||||||
|
_, err = outConn.Write(utils.BuildPacket(typ, s.serverID))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("write connection data err: %s ,retrying...", err)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err == nil {
|
||||||
|
conn = net.Conn(&_conn)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func NewTunnelServer() Service {
|
||||||
|
return &TunnelServer{
|
||||||
|
cfg: TunnelServerArgs{},
|
||||||
|
udpChn: make(chan UDPItem, 50000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPItem struct {
|
||||||
|
packet *[]byte
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
srcAddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TunnelServer) InitService() {
|
||||||
|
s.UDPConnDeamon()
|
||||||
|
}
|
||||||
|
func (s *TunnelServer) CheckArgs() {
|
||||||
|
if *s.cfg.Remote == "" {
|
||||||
|
log.Fatalf("remote required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TunnelServer) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(TunnelServerArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
s.InitService()
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
s.sc = utils.NewServerChannel(host, p)
|
||||||
|
if *s.cfg.IsUDP {
|
||||||
|
err = s.sc.ListenUDP(func(packet []byte, localAddr, srcAddr *net.UDPAddr) {
|
||||||
|
s.udpChn <- UDPItem{
|
||||||
|
packet: &packet,
|
||||||
|
localAddr: localAddr,
|
||||||
|
srcAddr: srcAddr,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var outConn net.Conn
|
||||||
|
var ID string
|
||||||
|
for {
|
||||||
|
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)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("proxy on tunnel server mode %s", (*s.sc.Listener).Addr())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remoteAddr := "tcp:" + *s.cfg.Remote
|
||||||
|
if *s.cfg.IsUDP {
|
||||||
|
remoteAddr = "udp:" + *s.cfg.Remote
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
utils.CloseConn(&outConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err == nil {
|
||||||
|
conn = net.Conn(&_conn)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var outConn net.Conn
|
||||||
|
// var hb utils.HeartbeatReadWriter
|
||||||
|
var ID string
|
||||||
|
// var cmdChn = make(chan bool, 1000)
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
item := <-s.udpChn
|
||||||
|
RETRY:
|
||||||
|
if outConn == nil {
|
||||||
|
for {
|
||||||
|
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)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
go func(outConn net.Conn, ID string) {
|
||||||
|
go func() {
|
||||||
|
// <-cmdChn
|
||||||
|
// outConn.Close()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn)
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
log.Printf("UDP deamon connection %s exited", ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
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)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//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
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
219
services/udp.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"proxy/utils"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UDP struct {
|
||||||
|
p utils.ConcurrentMap
|
||||||
|
outPool utils.OutPool
|
||||||
|
cfg UDPArgs
|
||||||
|
sc *utils.ServerChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDP() Service {
|
||||||
|
return &UDP{
|
||||||
|
outPool: utils.OutPool{},
|
||||||
|
p: utils.NewConcurrentMap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *UDP) CheckArgs() {
|
||||||
|
if *s.cfg.Parent == "" {
|
||||||
|
log.Fatalf("parent required for udp %s", *s.cfg.Local)
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "" {
|
||||||
|
log.Fatalf("parent type unkown,use -T <tls|tcp>")
|
||||||
|
}
|
||||||
|
if *s.cfg.ParentType == "tls" {
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *UDP) InitService() {
|
||||||
|
if *s.cfg.ParentType != TYPE_UDP {
|
||||||
|
s.InitOutConnPool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *UDP) StopService() {
|
||||||
|
if s.outPool.Pool != nil {
|
||||||
|
s.outPool.Pool.ReleaseAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *UDP) Start(args interface{}) (err error) {
|
||||||
|
s.cfg = args.(UDPArgs)
|
||||||
|
s.CheckArgs()
|
||||||
|
log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent)
|
||||||
|
s.InitService()
|
||||||
|
|
||||||
|
host, port, _ := net.SplitHostPort(*s.cfg.Local)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
sc := utils.NewServerChannel(host, p)
|
||||||
|
s.sc = &sc
|
||||||
|
err = sc.ListenUDP(s.callback)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("udp proxy on %s", (*sc.UDPListener).LocalAddr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDP) Clean() {
|
||||||
|
s.StopService()
|
||||||
|
}
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
switch *s.cfg.ParentType {
|
||||||
|
case TYPE_TCP:
|
||||||
|
fallthrough
|
||||||
|
case TYPE_TLS:
|
||||||
|
err = s.OutToTCP(packet, localAddr, srcAddr)
|
||||||
|
case TYPE_UDP:
|
||||||
|
err = s.OutToUDP(packet, localAddr, srcAddr)
|
||||||
|
default:
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
s.p.Set(connKey, _conn)
|
||||||
|
} else {
|
||||||
|
_conn, _ = s.p.Get(connKey)
|
||||||
|
}
|
||||||
|
conn = _conn.(net.Conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Printf("conn %d created , local: %s", connKey, srcAddr.String())
|
||||||
|
for {
|
||||||
|
srcAddrFromConn, body, err := utils.ReadUDPPacket(bufio.NewReader(conn))
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
//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)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
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)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//log.Printf("udp response to local %s success", srcAddr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//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.cfg.CheckParentInterval,
|
||||||
|
*s.cfg.ParentType,
|
||||||
|
"", "",
|
||||||
|
s.cfg.CertBytes, s.cfg.KeyBytes,
|
||||||
|
*s.cfg.Parent,
|
||||||
|
*s.cfg.Timeout,
|
||||||
|
*s.cfg.PoolSize,
|
||||||
|
*s.cfg.PoolSize*2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
rm -rf /usr/bin/proxy
|
rm -rf /usr/bin/proxy
|
||||||
rm -rf /usr/bin/proxyd
|
|
||||||
echo "uninstall done"
|
echo "uninstall done"
|
||||||
|
|||||||
84
utils/aes/aes.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Playbook - http://play.golang.org/p/3wFl4lacjX
|
||||||
|
|
||||||
|
package goaes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addBase64Padding(value string) string {
|
||||||
|
m := len(value) % 4
|
||||||
|
if m != 0 {
|
||||||
|
value += strings.Repeat("=", 4-m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeBase64Padding(value string) string {
|
||||||
|
return strings.Replace(value, "=", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pad(src []byte) []byte {
|
||||||
|
padding := aes.BlockSize - len(src)%aes.BlockSize
|
||||||
|
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||||
|
return append(src, padtext...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unpad(src []byte) ([]byte, error) {
|
||||||
|
length := len(src)
|
||||||
|
unpadding := int(src[length-1])
|
||||||
|
|
||||||
|
if unpadding > length {
|
||||||
|
return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")
|
||||||
|
}
|
||||||
|
|
||||||
|
return src[:(length - unpadding)], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Encrypt(key []byte, text []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := Pad(text)
|
||||||
|
ciphertext := make([]byte, aes.BlockSize+len(msg))
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
|
||||||
|
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decrypt(key []byte, text []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if (len(text) % aes.BlockSize) != 0 {
|
||||||
|
return nil, errors.New("blocksize must be multipe of decoded message length")
|
||||||
|
}
|
||||||
|
iv := text[:aes.BlockSize]
|
||||||
|
msg := text[aes.BlockSize:]
|
||||||
|
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(msg, msg)
|
||||||
|
|
||||||
|
unpadMsg, err := Unpad(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return unpadMsg, nil
|
||||||
|
}
|
||||||
536
utils/functions.go
Executable file
@ -0,0 +1,536 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
"proxy/utils/id"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
kcp "github.com/xtaci/kcp-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IoBind(dst io.ReadWriteCloser, src io.ReadWriteCloser, fn func(err interface{})) {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("bind crashed %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
e1 := make(chan interface{}, 1)
|
||||||
|
e2 := make(chan interface{}, 1)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("bind crashed %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
//_, err := io.Copy(dst, src)
|
||||||
|
err := ioCopy(dst, src)
|
||||||
|
e1 <- err
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("bind crashed %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
//_, err := io.Copy(src, dst)
|
||||||
|
err := ioCopy(src, dst)
|
||||||
|
e2 <- err
|
||||||
|
}()
|
||||||
|
var err interface{}
|
||||||
|
select {
|
||||||
|
case err = <-e1:
|
||||||
|
//log.Printf("e1")
|
||||||
|
case err = <-e2:
|
||||||
|
//log.Printf("e2")
|
||||||
|
}
|
||||||
|
src.Close()
|
||||||
|
dst.Close()
|
||||||
|
fn(err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
n := 0
|
||||||
|
for {
|
||||||
|
n, err = src.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
if _, e := dst.Write(buf[0:n]); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TlsConnectHost(host string, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) {
|
||||||
|
h := strings.Split(host, ":")
|
||||||
|
port, _ := strconv.Atoi(h[1])
|
||||||
|
return TlsConnect(h[0], port, timeout, certBytes, keyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) {
|
||||||
|
conf, err := getRequestTlsConfig(certBytes, keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Duration(timeout)*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return *tls.Client(_conn, conf), err
|
||||||
|
}
|
||||||
|
func getRequestTlsConfig(certBytes, keyBytes []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)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("failed to parse root certificate")
|
||||||
|
}
|
||||||
|
conf = &tls.Config{
|
||||||
|
RootCAs: serverCertPool,
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
ServerName: "proxy",
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kcpconn.SetNoDelay(1, 10, 2, 1)
|
||||||
|
kcpconn.SetWindowSize(1024, 1024)
|
||||||
|
kcpconn.SetMtu(1400)
|
||||||
|
kcpconn.SetACKNoDelay(false)
|
||||||
|
return 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) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func HTTPGet(URL string, timeout int) (err error) {
|
||||||
|
tr := &http.Transport{}
|
||||||
|
var resp *http.Response
|
||||||
|
var client *http.Client
|
||||||
|
defer func() {
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
tr.CloseIdleConnections()
|
||||||
|
}()
|
||||||
|
client = &http.Client{Timeout: time.Millisecond * time.Duration(timeout), Transport: tr}
|
||||||
|
resp, err = client.Get(URL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseConn(conn *net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
_ = recover()
|
||||||
|
}()
|
||||||
|
if conn != nil && *conn != nil {
|
||||||
|
(*conn).SetDeadline(time.Now().Add(time.Millisecond))
|
||||||
|
(*conn).Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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`)
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func GetAllInterfaceAddr() ([]net.IP, error) {
|
||||||
|
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addresses := []net.IP{}
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
|
||||||
|
if iface.Flags&net.FlagUp == 0 {
|
||||||
|
continue // interface down
|
||||||
|
}
|
||||||
|
// if iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
// continue // loopback interface
|
||||||
|
// }
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
switch v := addr.(type) {
|
||||||
|
case *net.IPNet:
|
||||||
|
ip = v.IP
|
||||||
|
case *net.IPAddr:
|
||||||
|
ip = v.IP
|
||||||
|
}
|
||||||
|
// if ip == nil || ip.IsLoopback() {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
ip = ip.To4()
|
||||||
|
if ip == nil {
|
||||||
|
continue // not an ipv4 address
|
||||||
|
}
|
||||||
|
addresses = append(addresses, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
return nil, fmt.Errorf("no address Found, net.InterfaceAddrs: %v", addresses)
|
||||||
|
}
|
||||||
|
//only need first
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
func UDPPacket(srcAddr string, packet []byte) []byte {
|
||||||
|
addrBytes := []byte(srcAddr)
|
||||||
|
addrLength := uint16(len(addrBytes))
|
||||||
|
bodyLength := uint16(len(packet))
|
||||||
|
//log.Printf("build packet : addr len %d, body len %d", addrLength, bodyLength)
|
||||||
|
pkg := new(bytes.Buffer)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, addrLength)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, addrBytes)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, bodyLength)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, packet)
|
||||||
|
return pkg.Bytes()
|
||||||
|
}
|
||||||
|
func ReadUDPPacket(_reader io.Reader) (srcAddr string, packet []byte, err error) {
|
||||||
|
reader := bufio.NewReader(_reader)
|
||||||
|
var addrLength uint16
|
||||||
|
var bodyLength uint16
|
||||||
|
err = binary.Read(reader, binary.LittleEndian, &addrLength)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_srcAddr := make([]byte, addrLength)
|
||||||
|
n, err := reader.Read(_srcAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != int(addrLength) {
|
||||||
|
err = fmt.Errorf("n != int(addrLength), %d,%d", n, addrLength)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcAddr = string(_srcAddr)
|
||||||
|
|
||||||
|
err = binary.Read(reader, binary.LittleEndian, &bodyLength)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet = make([]byte, bodyLength)
|
||||||
|
n, err = reader.Read(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != int(bodyLength) {
|
||||||
|
err = fmt.Errorf("n != int(bodyLength), %d,%d", n, bodyLength)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func Uniqueid() string {
|
||||||
|
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 ReadData(r io.Reader) (data string, err error) {
|
||||||
|
var len uint16
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &len)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
_data := make([]byte, len)
|
||||||
|
n, err = r.Read(_data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != int(len) {
|
||||||
|
err = fmt.Errorf("error data len")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = string(_data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func ReadPacketData(r io.Reader, data ...*string) (err error) {
|
||||||
|
for _, d := range data {
|
||||||
|
*d, err = ReadData(r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func ReadPacket(r io.Reader, typ *uint8, data ...*string) (err error) {
|
||||||
|
var connType uint8
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &connType)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*typ = connType
|
||||||
|
for _, d := range data {
|
||||||
|
*d, err = ReadData(r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func BuildPacket(typ uint8, data ...string) []byte {
|
||||||
|
pkg := new(bytes.Buffer)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, typ)
|
||||||
|
for _, d := range data {
|
||||||
|
bytes := []byte(d)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, uint16(len(bytes)))
|
||||||
|
binary.Write(pkg, binary.LittleEndian, bytes)
|
||||||
|
}
|
||||||
|
return pkg.Bytes()
|
||||||
|
}
|
||||||
|
func BuildPacketData(data ...string) []byte {
|
||||||
|
pkg := new(bytes.Buffer)
|
||||||
|
for _, d := range data {
|
||||||
|
bytes := []byte(d)
|
||||||
|
binary.Write(pkg, binary.LittleEndian, uint16(len(bytes)))
|
||||||
|
binary.Write(pkg, binary.LittleEndian, bytes)
|
||||||
|
}
|
||||||
|
return pkg.Bytes()
|
||||||
|
}
|
||||||
|
func SubStr(str string, start, end int) string {
|
||||||
|
if len(str) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if end >= len(str) {
|
||||||
|
end = len(str) - 1
|
||||||
|
}
|
||||||
|
return str[start:end]
|
||||||
|
}
|
||||||
|
func SubBytes(bytes []byte, start, end int) []byte {
|
||||||
|
if len(bytes) == 0 {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
if end >= len(bytes) {
|
||||||
|
end = len(bytes) - 1
|
||||||
|
}
|
||||||
|
return bytes[start:end]
|
||||||
|
}
|
||||||
|
func TlsBytes(cert, key string) (certBytes, keyBytes []byte) {
|
||||||
|
certBytes, err := ioutil.ReadFile(cert)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("err : %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyBytes, err = ioutil.ReadFile(key)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("err : %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func GetKCPBlock(method, key string) (block kcp.BlockCrypt) {
|
||||||
|
pass := pbkdf2.Key([]byte(key), []byte(key), 4096, 32, sha1.New)
|
||||||
|
switch method {
|
||||||
|
case "sm4":
|
||||||
|
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||||
|
case "tea":
|
||||||
|
block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||||
|
case "xor":
|
||||||
|
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||||
|
case "none":
|
||||||
|
block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||||
|
case "aes-128":
|
||||||
|
block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||||
|
case "aes-192":
|
||||||
|
block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||||
|
case "blowfish":
|
||||||
|
block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||||
|
case "twofish":
|
||||||
|
block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||||
|
case "cast5":
|
||||||
|
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||||
|
case "3des":
|
||||||
|
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||||
|
case "xtea":
|
||||||
|
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||||
|
case "salsa20":
|
||||||
|
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||||
|
default:
|
||||||
|
block, _ = kcp.NewAESBlockCrypt(pass)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func HttpGet(URL string, timeout int) (body []byte, code int, err error) {
|
||||||
|
var tr *http.Transport
|
||||||
|
var client *http.Client
|
||||||
|
conf := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
if strings.Contains(URL, "https://") {
|
||||||
|
tr = &http.Transport{TLSClientConfig: conf}
|
||||||
|
client = &http.Client{Timeout: time.Millisecond * time.Duration(timeout), Transport: tr}
|
||||||
|
} else {
|
||||||
|
tr = &http.Transport{}
|
||||||
|
client = &http.Client{Timeout: time.Millisecond * time.Duration(timeout), Transport: tr}
|
||||||
|
}
|
||||||
|
defer tr.CloseIdleConnections()
|
||||||
|
resp, err := client.Get(URL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
code = resp.StatusCode
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func IsIternalIP(domainOrIP string) bool {
|
||||||
|
var outIPs []net.IP
|
||||||
|
outIPs, err := net.LookupIP(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// type sockaddr struct {
|
||||||
|
// family uint16
|
||||||
|
// data [14]byte
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const SO_ORIGINAL_DST = 80
|
||||||
|
|
||||||
|
// realServerAddress returns an intercepted connection's original destination.
|
||||||
|
// func realServerAddress(conn *net.Conn) (string, error) {
|
||||||
|
// tcpConn, ok := (*conn).(*net.TCPConn)
|
||||||
|
// if !ok {
|
||||||
|
// return "", errors.New("not a TCPConn")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// file, err := tcpConn.File()
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // To avoid potential problems from making the socket non-blocking.
|
||||||
|
// tcpConn.Close()
|
||||||
|
// *conn, err = net.FileConn(file)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// defer file.Close()
|
||||||
|
// fd := file.Fd()
|
||||||
|
|
||||||
|
// var addr sockaddr
|
||||||
|
// size := uint32(unsafe.Sizeof(addr))
|
||||||
|
// err = getsockopt(int(fd), syscall.SOL_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), &size)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var ip net.IP
|
||||||
|
// switch addr.family {
|
||||||
|
// case syscall.AF_INET:
|
||||||
|
// ip = addr.data[2:6]
|
||||||
|
// default:
|
||||||
|
// return "", errors.New("unrecognized address family")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// port := int(addr.data[0])<<8 + int(addr.data[1])
|
||||||
|
|
||||||
|
// return net.JoinHostPort(ip.String(), strconv.Itoa(port)), nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func getsockopt(s int, level int, name int, val uintptr, vallen *uint32) (err error) {
|
||||||
|
// _, _, e1 := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
|
||||||
|
// if e1 != 0 {
|
||||||
|
// err = e1
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
264
utils/id/xid.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Package xid is a globally unique id generator suited for web scale
|
||||||
|
//
|
||||||
|
// Xid is using Mongo Object ID algorithm to generate globally unique ids:
|
||||||
|
// https://docs.mongodb.org/manual/reference/object-id/
|
||||||
|
//
|
||||||
|
// - 4-byte value representing the seconds since the Unix epoch,
|
||||||
|
// - 3-byte machine identifier,
|
||||||
|
// - 2-byte process id, and
|
||||||
|
// - 3-byte counter, starting with a random value.
|
||||||
|
//
|
||||||
|
// The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
|
||||||
|
// The string representation is using base32 hex (w/o padding) for better space efficiency
|
||||||
|
// when stored in that form (20 bytes). The hex variant of base32 is used to retain the
|
||||||
|
// sortable property of the id.
|
||||||
|
//
|
||||||
|
// Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an
|
||||||
|
// issue when transported as a string between various systems. Base36 wasn't retained either
|
||||||
|
// because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned)
|
||||||
|
// and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long,
|
||||||
|
// all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`).
|
||||||
|
//
|
||||||
|
// UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between
|
||||||
|
// with 12 bytes with a more compact string representation ready for the web and no
|
||||||
|
// required configuration or central generation server.
|
||||||
|
//
|
||||||
|
// Features:
|
||||||
|
//
|
||||||
|
// - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
|
||||||
|
// - Base32 hex encoded by default (16 bytes storage when transported as printable string)
|
||||||
|
// - Non configured, you don't need set a unique machine and/or data center id
|
||||||
|
// - K-ordered
|
||||||
|
// - Embedded time with 1 second precision
|
||||||
|
// - Unicity guaranted for 16,777,216 (24 bits) unique ids per second and per host/process
|
||||||
|
//
|
||||||
|
// Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler).
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
//
|
||||||
|
// - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
|
||||||
|
// - https://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||||
|
// - https://blog.twitter.com/2010/announcing-snowflake
|
||||||
|
package xid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Code inspired from mgo/bson ObjectId
|
||||||
|
|
||||||
|
// ID represents a unique request id
|
||||||
|
type ID [rawLen]byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
encodedLen = 20 // string encoded len
|
||||||
|
decodedLen = 15 // len after base32 decoding with the padded data
|
||||||
|
rawLen = 12 // binary raw len
|
||||||
|
|
||||||
|
// encoding stores a custom version of the base32 encoding with lower case
|
||||||
|
// letters.
|
||||||
|
encoding = "0123456789abcdefghijklmnopqrstuv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidID is returned when trying to unmarshal an invalid ID
|
||||||
|
var ErrInvalidID = errors.New("xid: invalid ID")
|
||||||
|
|
||||||
|
// objectIDCounter is atomically incremented when generating a new ObjectId
|
||||||
|
// using NewObjectId() function. It's used as a counter part of an id.
|
||||||
|
// This id is initialized with a random value.
|
||||||
|
var objectIDCounter = randInt()
|
||||||
|
|
||||||
|
// machineId stores machine id generated once and used in subsequent calls
|
||||||
|
// to NewObjectId function.
|
||||||
|
var machineID = readMachineID()
|
||||||
|
|
||||||
|
// pid stores the current process id
|
||||||
|
var pid = os.Getpid()
|
||||||
|
|
||||||
|
// dec is the decoding map for base32 encoding
|
||||||
|
var dec [256]byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := 0; i < len(dec); i++ {
|
||||||
|
dec[i] = 0xFF
|
||||||
|
}
|
||||||
|
for i := 0; i < len(encoding); i++ {
|
||||||
|
dec[encoding[i]] = byte(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMachineId generates machine id and puts it into the machineId global
|
||||||
|
// variable. If this function fails to get the hostname, it will cause
|
||||||
|
// a runtime error.
|
||||||
|
func readMachineID() []byte {
|
||||||
|
id := make([]byte, 3)
|
||||||
|
if hostname, err := os.Hostname(); err == nil {
|
||||||
|
hw := md5.New()
|
||||||
|
hw.Write([]byte(hostname))
|
||||||
|
copy(id, hw.Sum(nil))
|
||||||
|
} else {
|
||||||
|
// Fallback to rand number if machine id can't be gathered
|
||||||
|
if _, randErr := rand.Reader.Read(id); randErr != nil {
|
||||||
|
panic(fmt.Errorf("xid: cannot get hostname nor generate a random number: %v; %v", err, randErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// randInt generates a random uint32
|
||||||
|
func randInt() uint32 {
|
||||||
|
b := make([]byte, 3)
|
||||||
|
if _, err := rand.Reader.Read(b); err != nil {
|
||||||
|
panic(fmt.Errorf("xid: cannot generate random number: %v;", err))
|
||||||
|
}
|
||||||
|
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// New generates a globaly unique ID
|
||||||
|
func New() ID {
|
||||||
|
var id ID
|
||||||
|
// Timestamp, 4 bytes, big endian
|
||||||
|
binary.BigEndian.PutUint32(id[:], uint32(time.Now().Unix()))
|
||||||
|
// Machine, first 3 bytes of md5(hostname)
|
||||||
|
id[4] = machineID[0]
|
||||||
|
id[5] = machineID[1]
|
||||||
|
id[6] = machineID[2]
|
||||||
|
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
|
||||||
|
id[7] = byte(pid >> 8)
|
||||||
|
id[8] = byte(pid)
|
||||||
|
// Increment, 3 bytes, big endian
|
||||||
|
i := atomic.AddUint32(&objectIDCounter, 1)
|
||||||
|
id[9] = byte(i >> 16)
|
||||||
|
id[10] = byte(i >> 8)
|
||||||
|
id[11] = byte(i)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString reads an ID from its string representation
|
||||||
|
func FromString(id string) (ID, error) {
|
||||||
|
i := &ID{}
|
||||||
|
err := i.UnmarshalText([]byte(id))
|
||||||
|
return *i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a base32 hex lowercased with no padding representation of the id (char set is 0-9, a-v).
|
||||||
|
func (id ID) String() string {
|
||||||
|
text := make([]byte, encodedLen)
|
||||||
|
encode(text, id[:])
|
||||||
|
return string(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding/text TextMarshaler interface
|
||||||
|
func (id ID) MarshalText() ([]byte, error) {
|
||||||
|
text := make([]byte, encodedLen)
|
||||||
|
encode(text, id[:])
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode by unrolling the stdlib base32 algorithm + removing all safe checks
|
||||||
|
func encode(dst, id []byte) {
|
||||||
|
dst[0] = encoding[id[0]>>3]
|
||||||
|
dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F]
|
||||||
|
dst[2] = encoding[(id[1]>>1)&0x1F]
|
||||||
|
dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F]
|
||||||
|
dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F]
|
||||||
|
dst[5] = encoding[(id[3]>>2)&0x1F]
|
||||||
|
dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F]
|
||||||
|
dst[7] = encoding[id[4]&0x1F]
|
||||||
|
dst[8] = encoding[id[5]>>3]
|
||||||
|
dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F]
|
||||||
|
dst[10] = encoding[(id[6]>>1)&0x1F]
|
||||||
|
dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F]
|
||||||
|
dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F]
|
||||||
|
dst[13] = encoding[(id[8]>>2)&0x1F]
|
||||||
|
dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F]
|
||||||
|
dst[15] = encoding[id[9]&0x1F]
|
||||||
|
dst[16] = encoding[id[10]>>3]
|
||||||
|
dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
|
||||||
|
dst[18] = encoding[(id[11]>>1)&0x1F]
|
||||||
|
dst[19] = encoding[(id[11]<<4)&0x1F]
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding/text TextUnmarshaler interface
|
||||||
|
func (id *ID) UnmarshalText(text []byte) error {
|
||||||
|
if len(text) != encodedLen {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
for _, c := range text {
|
||||||
|
if dec[c] == 0xFF {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decode(id, text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode by unrolling the stdlib base32 algorithm + removing all safe checks
|
||||||
|
func decode(id *ID, src []byte) {
|
||||||
|
id[0] = dec[src[0]]<<3 | dec[src[1]]>>2
|
||||||
|
id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4
|
||||||
|
id[2] = dec[src[3]]<<4 | dec[src[4]]>>1
|
||||||
|
id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3
|
||||||
|
id[4] = dec[src[6]]<<5 | dec[src[7]]
|
||||||
|
id[5] = dec[src[8]]<<3 | dec[src[9]]>>2
|
||||||
|
id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4
|
||||||
|
id[7] = dec[src[11]]<<4 | dec[src[12]]>>1
|
||||||
|
id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3
|
||||||
|
id[9] = dec[src[14]]<<5 | dec[src[15]]
|
||||||
|
id[10] = dec[src[16]]<<3 | dec[src[17]]>>2
|
||||||
|
id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the timestamp part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Time() time.Time {
|
||||||
|
// First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch.
|
||||||
|
secs := int64(binary.BigEndian.Uint32(id[0:4]))
|
||||||
|
return time.Unix(secs, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine returns the 3-byte machine id part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Machine() []byte {
|
||||||
|
return id[4:7]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pid returns the process id part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Pid() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(id[7:9])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter returns the incrementing value part of the id.
|
||||||
|
// It's a runtime error to call this method with an invalid id.
|
||||||
|
func (id ID) Counter() int32 {
|
||||||
|
b := id[9:12]
|
||||||
|
// Counter is stored as big-endian 3-byte value
|
||||||
|
return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (id ID) Value() (driver.Value, error) {
|
||||||
|
b, err := id.MarshalText()
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (id *ID) Scan(value interface{}) (err error) {
|
||||||
|
switch val := value.(type) {
|
||||||
|
case string:
|
||||||
|
return id.UnmarshalText([]byte(val))
|
||||||
|
case []byte:
|
||||||
|
return id.UnmarshalText(val)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("xid: scanning unsupported type: %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
97
utils/io-limiter.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const burstLimit = 1000 * 1000 * 1000
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
limiter *rate.Limiter
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
limiter *rate.Limiter
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a reader that implements io.Reader with rate limiting.
|
||||||
|
func NewReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReaderWithContext returns a reader that implements io.Reader with rate limiting.
|
||||||
|
func NewReaderWithContext(r io.Reader, ctx context.Context) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a writer that implements io.Writer with rate limiting.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriterWithContext returns a writer that implements io.Writer with rate limiting.
|
||||||
|
func NewWriterWithContext(w io.Writer, ctx context.Context) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRateLimit sets rate limit (bytes/sec) to the reader.
|
||||||
|
func (s *Reader) SetRateLimit(bytesPerSec float64) {
|
||||||
|
s.limiter = rate.NewLimiter(rate.Limit(bytesPerSec), burstLimit)
|
||||||
|
s.limiter.AllowN(time.Now(), burstLimit) // spend initial burst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes into p.
|
||||||
|
func (s *Reader) Read(p []byte) (int, error) {
|
||||||
|
if s.limiter == nil {
|
||||||
|
return s.r.Read(p)
|
||||||
|
}
|
||||||
|
n, err := s.r.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if err := s.limiter.WaitN(s.ctx, n); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRateLimit sets rate limit (bytes/sec) to the writer.
|
||||||
|
func (s *Writer) SetRateLimit(bytesPerSec float64) {
|
||||||
|
s.limiter = rate.NewLimiter(rate.Limit(bytesPerSec), burstLimit)
|
||||||
|
s.limiter.AllowN(time.Now(), burstLimit) // spend initial burst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes from p.
|
||||||
|
func (s *Writer) Write(p []byte) (int, error) {
|
||||||
|
if s.limiter == nil {
|
||||||
|
return s.w.Write(p)
|
||||||
|
}
|
||||||
|
n, err := s.w.Write(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if err := s.limiter.WaitN(s.ctx, n); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
315
utils/map.go
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SHARD_COUNT = 32
|
||||||
|
|
||||||
|
// A "thread" safe map of type string:Anything.
|
||||||
|
// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards.
|
||||||
|
type ConcurrentMap []*ConcurrentMapShared
|
||||||
|
|
||||||
|
// A "thread" safe string to anything map.
|
||||||
|
type ConcurrentMapShared struct {
|
||||||
|
items map[string]interface{}
|
||||||
|
sync.RWMutex // Read Write mutex, guards access to internal map.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new concurrent map.
|
||||||
|
func NewConcurrentMap() ConcurrentMap {
|
||||||
|
m := make(ConcurrentMap, SHARD_COUNT)
|
||||||
|
for i := 0; i < SHARD_COUNT; i++ {
|
||||||
|
m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns shard under given key
|
||||||
|
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
|
||||||
|
return m[uint(fnv32(key))%uint(SHARD_COUNT)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m ConcurrentMap) MSet(data map[string]interface{}) {
|
||||||
|
for key, value := range data {
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.Lock()
|
||||||
|
shard.items[key] = value
|
||||||
|
shard.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the given value under the specified key.
|
||||||
|
func (m ConcurrentMap) Set(key string, value interface{}) {
|
||||||
|
// Get map shard.
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.Lock()
|
||||||
|
shard.items[key] = value
|
||||||
|
shard.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback to return new element to be inserted into the map
|
||||||
|
// It is called while lock is held, therefore it MUST NOT
|
||||||
|
// try to access other keys in same map, as it can lead to deadlock since
|
||||||
|
// Go sync.RWLock is not reentrant
|
||||||
|
type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{}
|
||||||
|
|
||||||
|
// Insert or Update - updates existing element or inserts a new one using UpsertCb
|
||||||
|
func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) {
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.Lock()
|
||||||
|
v, ok := shard.items[key]
|
||||||
|
res = cb(ok, v, value)
|
||||||
|
shard.items[key] = res
|
||||||
|
shard.Unlock()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the given value under the specified key if no value was associated with it.
|
||||||
|
func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool {
|
||||||
|
// Get map shard.
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.Lock()
|
||||||
|
_, ok := shard.items[key]
|
||||||
|
if !ok {
|
||||||
|
shard.items[key] = value
|
||||||
|
}
|
||||||
|
shard.Unlock()
|
||||||
|
return !ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves an element from map under given key.
|
||||||
|
func (m ConcurrentMap) Get(key string) (interface{}, bool) {
|
||||||
|
// Get shard
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.RLock()
|
||||||
|
// Get item from shard.
|
||||||
|
val, ok := shard.items[key]
|
||||||
|
shard.RUnlock()
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of elements within the map.
|
||||||
|
func (m ConcurrentMap) Count() int {
|
||||||
|
count := 0
|
||||||
|
for i := 0; i < SHARD_COUNT; i++ {
|
||||||
|
shard := m[i]
|
||||||
|
shard.RLock()
|
||||||
|
count += len(shard.items)
|
||||||
|
shard.RUnlock()
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up an item under specified key
|
||||||
|
func (m ConcurrentMap) Has(key string) bool {
|
||||||
|
// Get shard
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.RLock()
|
||||||
|
// See if element is within shard.
|
||||||
|
_, ok := shard.items[key]
|
||||||
|
shard.RUnlock()
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes an element from the map.
|
||||||
|
func (m ConcurrentMap) Remove(key string) {
|
||||||
|
// Try to get shard.
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.Lock()
|
||||||
|
delete(shard.items, key)
|
||||||
|
shard.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes an element from the map and returns it
|
||||||
|
func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) {
|
||||||
|
// Try to get shard.
|
||||||
|
shard := m.GetShard(key)
|
||||||
|
shard.Lock()
|
||||||
|
v, exists = shard.items[key]
|
||||||
|
delete(shard.items, key)
|
||||||
|
shard.Unlock()
|
||||||
|
return v, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if map is empty.
|
||||||
|
func (m ConcurrentMap) IsEmpty() bool {
|
||||||
|
return m.Count() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the Iter & IterBuffered functions to wrap two variables together over a channel,
|
||||||
|
type Tuple struct {
|
||||||
|
Key string
|
||||||
|
Val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator which could be used in a for range loop.
|
||||||
|
//
|
||||||
|
// Deprecated: using IterBuffered() will get a better performence
|
||||||
|
func (m ConcurrentMap) Iter() <-chan Tuple {
|
||||||
|
chans := snapshot(m)
|
||||||
|
ch := make(chan Tuple)
|
||||||
|
go fanIn(chans, ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a buffered iterator which could be used in a for range loop.
|
||||||
|
func (m ConcurrentMap) IterBuffered() <-chan Tuple {
|
||||||
|
chans := snapshot(m)
|
||||||
|
total := 0
|
||||||
|
for _, c := range chans {
|
||||||
|
total += cap(c)
|
||||||
|
}
|
||||||
|
ch := make(chan Tuple, total)
|
||||||
|
go fanIn(chans, ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a array of channels that contains elements in each shard,
|
||||||
|
// which likely takes a snapshot of `m`.
|
||||||
|
// It returns once the size of each buffered channel is determined,
|
||||||
|
// before all the channels are populated using goroutines.
|
||||||
|
func snapshot(m ConcurrentMap) (chans []chan Tuple) {
|
||||||
|
chans = make([]chan Tuple, SHARD_COUNT)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(SHARD_COUNT)
|
||||||
|
// Foreach shard.
|
||||||
|
for index, shard := range m {
|
||||||
|
go func(index int, shard *ConcurrentMapShared) {
|
||||||
|
// Foreach key, value pair.
|
||||||
|
shard.RLock()
|
||||||
|
chans[index] = make(chan Tuple, len(shard.items))
|
||||||
|
wg.Done()
|
||||||
|
for key, val := range shard.items {
|
||||||
|
chans[index] <- Tuple{key, val}
|
||||||
|
}
|
||||||
|
shard.RUnlock()
|
||||||
|
close(chans[index])
|
||||||
|
}(index, shard)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return chans
|
||||||
|
}
|
||||||
|
|
||||||
|
// fanIn reads elements from channels `chans` into channel `out`
|
||||||
|
func fanIn(chans []chan Tuple, out chan Tuple) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(chans))
|
||||||
|
for _, ch := range chans {
|
||||||
|
go func(ch chan Tuple) {
|
||||||
|
for t := range ch {
|
||||||
|
out <- t
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}(ch)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns all items as map[string]interface{}
|
||||||
|
func (m ConcurrentMap) Items() map[string]interface{} {
|
||||||
|
tmp := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Insert items to temporary map.
|
||||||
|
for item := range m.IterBuffered() {
|
||||||
|
tmp[item.Key] = item.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator callback,called for every key,value found in
|
||||||
|
// maps. RLock is held for all calls for a given shard
|
||||||
|
// therefore callback sess consistent view of a shard,
|
||||||
|
// but not across the shards
|
||||||
|
type IterCb func(key string, v interface{})
|
||||||
|
|
||||||
|
// Callback based iterator, cheapest way to read
|
||||||
|
// all elements in a map.
|
||||||
|
func (m ConcurrentMap) IterCb(fn IterCb) {
|
||||||
|
for idx := range m {
|
||||||
|
shard := (m)[idx]
|
||||||
|
shard.RLock()
|
||||||
|
for key, value := range shard.items {
|
||||||
|
fn(key, value)
|
||||||
|
}
|
||||||
|
shard.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all keys as []string
|
||||||
|
func (m ConcurrentMap) Keys() []string {
|
||||||
|
count := m.Count()
|
||||||
|
ch := make(chan string, count)
|
||||||
|
go func() {
|
||||||
|
// Foreach shard.
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(SHARD_COUNT)
|
||||||
|
for _, shard := range m {
|
||||||
|
go func(shard *ConcurrentMapShared) {
|
||||||
|
// Foreach key, value pair.
|
||||||
|
shard.RLock()
|
||||||
|
for key := range shard.items {
|
||||||
|
ch <- key
|
||||||
|
}
|
||||||
|
shard.RUnlock()
|
||||||
|
wg.Done()
|
||||||
|
}(shard)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Generate keys
|
||||||
|
keys := make([]string, 0, count)
|
||||||
|
for k := range ch {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reviles ConcurrentMap "private" variables to json marshal.
|
||||||
|
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
|
||||||
|
// Create a temporary map, which will hold all item spread across shards.
|
||||||
|
tmp := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Insert items to temporary map.
|
||||||
|
for item := range m.IterBuffered() {
|
||||||
|
tmp[item.Key] = item.Val
|
||||||
|
}
|
||||||
|
return json.Marshal(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fnv32(key string) uint32 {
|
||||||
|
hash := uint32(2166136261)
|
||||||
|
const prime32 = uint32(16777619)
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
hash *= prime32
|
||||||
|
hash ^= uint32(key[i])
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concurrent map uses Interface{} as its value, therefor JSON Unmarshal
|
||||||
|
// will probably won't know which to type to unmarshal into, in such case
|
||||||
|
// we'll end up with a value of type map[string]interface{}, In most cases this isn't
|
||||||
|
// out value type, this is why we've decided to remove this functionality.
|
||||||
|
|
||||||
|
// func (m *ConcurrentMap) UnmarshalJSON(b []byte) (err error) {
|
||||||
|
// // Reverse process of Marshal.
|
||||||
|
|
||||||
|
// tmp := make(map[string]interface{})
|
||||||
|
|
||||||
|
// // Unmarshal into a single map.
|
||||||
|
// if err := json.Unmarshal(b, &tmp); err != nil {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // foreach key,value pair in temporary map insert into our concurrent map.
|
||||||
|
// for key, val := range tmp {
|
||||||
|
// m.Set(key, val)
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
145
utils/pool.go
Executable file
@ -0,0 +1,145 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
172
utils/serve-channel.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
kcp "github.com/xtaci/kcp-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerChannel struct {
|
||||||
|
ip string
|
||||||
|
port int
|
||||||
|
Listener *net.Listener
|
||||||
|
UDPListener *net.UDPConn
|
||||||
|
errAcceptHandler func(err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerChannel(ip string, port int) ServerChannel {
|
||||||
|
return ServerChannel{
|
||||||
|
ip: ip,
|
||||||
|
port: port,
|
||||||
|
errAcceptHandler: func(err error) {
|
||||||
|
fmt.Printf("accept error , ERR:%s", err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func NewServerChannelHost(host string) ServerChannel {
|
||||||
|
h, port, _ := net.SplitHostPort(host)
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
return ServerChannel{
|
||||||
|
ip: h,
|
||||||
|
port: p,
|
||||||
|
errAcceptHandler: func(err error) {
|
||||||
|
log.Printf("accept error , ERR:%s", err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err == nil {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("ListenTls crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var conn net.Conn
|
||||||
|
conn, err = (*sc.Listener).Accept()
|
||||||
|
if err == nil {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("tls connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn(conn)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
sc.errAcceptHandler(err)
|
||||||
|
(*sc.Listener).Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
if err == nil {
|
||||||
|
sc.Listener = &l
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("ListenTCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var conn net.Conn
|
||||||
|
conn, err = (*sc.Listener).Accept()
|
||||||
|
if err == nil {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("tcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn(conn)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
sc.errAcceptHandler(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *net.UDPAddr)) (err error) {
|
||||||
|
addr := &net.UDPAddr{IP: net.ParseIP(sc.ip), Port: sc.port}
|
||||||
|
l, err := net.ListenUDP("udp", addr)
|
||||||
|
if err == nil {
|
||||||
|
sc.UDPListener = l
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("ListenUDP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var buf = make([]byte, 2048)
|
||||||
|
n, srcAddr, err := (*sc.UDPListener).ReadFromUDP(buf)
|
||||||
|
if err == nil {
|
||||||
|
packet := buf[0:n]
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("udp data handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn(packet, addr, srcAddr)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
sc.errAcceptHandler(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err == nil {
|
||||||
|
sc.Listener = &l
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
log.Printf("ListenKCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var conn net.Conn
|
||||||
|
conn, err = (*sc.Listener).Accept()
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn(conn)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
sc.errAcceptHandler(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
173
utils/sni/sni.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package sni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServerNameFromBytes(data []byte) (sn string, err error) {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
bufferedReader := bufio.NewReader(reader)
|
||||||
|
c := bufferedConn{bufferedReader, nil, nil}
|
||||||
|
sn, _, err = ServerNameFromConn(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type bufferedConn struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
rout io.Reader
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufferedConn(c net.Conn) bufferedConn {
|
||||||
|
return bufferedConn{bufio.NewReader(c), nil, c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bufferedConn) Peek(n int) ([]byte, error) {
|
||||||
|
return b.r.Peek(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bufferedConn) Read(p []byte) (int, error) {
|
||||||
|
if b.rout != nil {
|
||||||
|
return b.rout.Read(p)
|
||||||
|
}
|
||||||
|
return b.r.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var malformedError = errors.New("malformed client hello")
|
||||||
|
|
||||||
|
func getHello(b []byte) (string, error) {
|
||||||
|
rest := b[5:]
|
||||||
|
|
||||||
|
if len(rest) == 0 {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
current := 0
|
||||||
|
handshakeType := rest[0]
|
||||||
|
current += 1
|
||||||
|
if handshakeType != 0x1 {
|
||||||
|
return "", errors.New("Not a ClientHello")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over another length
|
||||||
|
current += 3
|
||||||
|
// Skip over protocolversion
|
||||||
|
current += 2
|
||||||
|
// Skip over random number
|
||||||
|
current += 4 + 28
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over session ID
|
||||||
|
sessionIDLength := int(rest[current])
|
||||||
|
current += 1
|
||||||
|
current += sessionIDLength
|
||||||
|
|
||||||
|
if current+1 > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
current += cipherSuiteLength
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
compressionMethodLength := int(rest[current])
|
||||||
|
current += 1
|
||||||
|
current += compressionMethodLength
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", errors.New("no extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
hostname := ""
|
||||||
|
for current+4 < len(rest) && hostname == "" {
|
||||||
|
extensionType := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
extensionDataLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
if extensionType == 0 {
|
||||||
|
|
||||||
|
// Skip over number of names as we're assuming there's just one
|
||||||
|
current += 2
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
|
||||||
|
nameType := rest[current]
|
||||||
|
current += 1
|
||||||
|
if nameType != 0 {
|
||||||
|
return "", errors.New("Not a hostname")
|
||||||
|
}
|
||||||
|
if current+1 > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
nameLen := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
if current+nameLen > len(rest) {
|
||||||
|
return "", malformedError
|
||||||
|
}
|
||||||
|
hostname = string(rest[current : current+nameLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
current += extensionDataLength
|
||||||
|
}
|
||||||
|
if hostname == "" {
|
||||||
|
return "", errors.New("No hostname")
|
||||||
|
}
|
||||||
|
return hostname, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHelloBytes(c bufferedConn) ([]byte, error) {
|
||||||
|
b, err := c.Peek(5)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != 0x16 {
|
||||||
|
return []byte{}, errors.New("not TLS")
|
||||||
|
}
|
||||||
|
|
||||||
|
restLengthBytes := b[3:]
|
||||||
|
restLength := (int(restLengthBytes[0]) << 8) + int(restLengthBytes[1])
|
||||||
|
|
||||||
|
return c.Peek(5 + restLength)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServername(c bufferedConn) (string, []byte, error) {
|
||||||
|
all, err := getHelloBytes(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
name, err := getHello(all)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return name, all, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uses SNI to get the name of the server from the connection. Returns the ServerName and a buffered connection that will not have been read off of.
|
||||||
|
func ServerNameFromConn(c net.Conn) (string, net.Conn, error) {
|
||||||
|
bufconn := newBufferedConn(c)
|
||||||
|
sn, helloBytes, err := getServername(bufconn)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
bufconn.rout = io.MultiReader(bytes.NewBuffer(helloBytes), c)
|
||||||
|
return sn, bufconn, nil
|
||||||
|
}
|
||||||
260
utils/socks/structs.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
package socks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"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
|
||||||
|
reserve uint8
|
||||||
|
addressType uint8
|
||||||
|
dstAddr string
|
||||||
|
dstPort string
|
||||||
|
dstHost string
|
||||||
|
bytes []byte
|
||||||
|
rw io.ReadWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequest(rw io.ReadWriter) (req Request, err interface{}) {
|
||||||
|
var b [1024]byte
|
||||||
|
var n int
|
||||||
|
req = Request{rw: rw}
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch b[3] {
|
||||||
|
case 0x01: //IP V4
|
||||||
|
req.dstHost = net.IPv4(b[4], b[5], b[6], b[7]).String()
|
||||||
|
case 0x03: //域名
|
||||||
|
req.dstHost = string(b[5 : n-2]) //b[4]表示域名的长度
|
||||||
|
case 0x04: //IP V6
|
||||||
|
req.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()
|
||||||
|
}
|
||||||
|
req.dstPort = strconv.Itoa(int(b[n-2])<<8 | int(b[n-1]))
|
||||||
|
req.dstAddr = net.JoinHostPort(req.dstHost, req.dstPort)
|
||||||
|
req.bytes = b[:n]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Request) Bytes() []byte {
|
||||||
|
return s.bytes
|
||||||
|
}
|
||||||
|
func (s *Request) Addr() string {
|
||||||
|
return s.dstAddr
|
||||||
|
}
|
||||||
|
func (s *Request) Host() string {
|
||||||
|
return s.dstHost
|
||||||
|
}
|
||||||
|
func (s *Request) Port() string {
|
||||||
|
return s.dstPort
|
||||||
|
}
|
||||||
|
func (s *Request) AType() uint8 {
|
||||||
|
return s.addressType
|
||||||
|
}
|
||||||
|
func (s *Request) CMD() uint8 {
|
||||||
|
return s.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Request) TCPReply(rep uint8) (err error) {
|
||||||
|
_, err = s.rw.Write(s.NewReply(rep, "0.0.0.0:0"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Request) UDPReply(rep uint8, addr string) (err error) {
|
||||||
|
_, err = s.rw.Write(s.NewReply(rep, addr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Request) NewReply(rep uint8, addr string) []byte {
|
||||||
|
var response bytes.Buffer
|
||||||
|
host, port, _ := net.SplitHostPort(addr)
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
ipb := ip.To4()
|
||||||
|
atyp := ATYP_IPV4
|
||||||
|
ipv6 := ip.To16()
|
||||||
|
zeroiIPv6 := fmt.Sprintf("%d%d%d%d%d%d%d%d%d%d%d%d",
|
||||||
|
ipv6[0], ipv6[1], ipv6[2], ipv6[3],
|
||||||
|
ipv6[4], ipv6[5], ipv6[6], ipv6[7],
|
||||||
|
ipv6[8], ipv6[9], ipv6[10], ipv6[11],
|
||||||
|
)
|
||||||
|
if ipv6 != nil && "0000000000255255" != zeroiIPv6 {
|
||||||
|
atyp = ATYP_IPV6
|
||||||
|
ipb = ip.To16()
|
||||||
|
}
|
||||||
|
porti, _ := strconv.Atoi(port)
|
||||||
|
portb := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(portb, uint16(porti))
|
||||||
|
// log.Printf("atyp : %v", atyp)
|
||||||
|
// log.Printf("ip : %v", []byte(ip))
|
||||||
|
response.WriteByte(VERSION_V5)
|
||||||
|
response.WriteByte(rep)
|
||||||
|
response.WriteByte(RSV)
|
||||||
|
response.WriteByte(atyp)
|
||||||
|
response.Write(ipb)
|
||||||
|
response.Write(portb)
|
||||||
|
return response.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
type MethodsRequest struct {
|
||||||
|
ver uint8
|
||||||
|
methodsCount uint8
|
||||||
|
methods []uint8
|
||||||
|
bytes []byte
|
||||||
|
rw *io.ReadWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMethodsRequest(r io.ReadWriter) (s MethodsRequest, err interface{}) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
err = recover()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s = MethodsRequest{}
|
||||||
|
s.rw = &r
|
||||||
|
var buf = make([]byte, 300)
|
||||||
|
var n int
|
||||||
|
n, err = r.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if buf[0] != 0x05 {
|
||||||
|
err = fmt.Errorf("socks version not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != int(buf[1])+int(2) {
|
||||||
|
err = fmt.Errorf("socks methods data length error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ver = buf[0]
|
||||||
|
s.methodsCount = buf[1]
|
||||||
|
s.methods = buf[2:n]
|
||||||
|
s.bytes = buf[:n]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *MethodsRequest) Version() uint8 {
|
||||||
|
return s.ver
|
||||||
|
}
|
||||||
|
func (s *MethodsRequest) MethodsCount() uint8 {
|
||||||
|
return s.methodsCount
|
||||||
|
}
|
||||||
|
func (s *MethodsRequest) Select(method uint8) bool {
|
||||||
|
for _, m := range s.methods {
|
||||||
|
if m == method {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (s *MethodsRequest) Reply(method uint8) (err error) {
|
||||||
|
_, err = (*s.rw).Write([]byte{byte(VERSION_V5), byte(method)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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])
|
||||||
|
p.bytes = b
|
||||||
|
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:]
|
||||||
|
p.header = b[:portIndex+2]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *UDPPacket) Header() []byte {
|
||||||
|
return s.header
|
||||||
|
}
|
||||||
|
func (s *UDPPacket) NewReply(data []byte) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Write(s.header)
|
||||||
|
buf.Write(data)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
func (s *UDPPacket) Host() string {
|
||||||
|
return s.dstHost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPPacket) Port() string {
|
||||||
|
return s.dstPort
|
||||||
|
}
|
||||||
|
func (s *UDPPacket) Data() []byte {
|
||||||
|
return s.data
|
||||||
|
}
|
||||||
775
utils/structs.go
Normal file
@ -0,0 +1,775 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"proxy/utils/sni"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Checker struct {
|
||||||
|
data ConcurrentMap
|
||||||
|
blockedMap ConcurrentMap
|
||||||
|
directMap ConcurrentMap
|
||||||
|
interval int64
|
||||||
|
timeout int
|
||||||
|
}
|
||||||
|
type CheckerItem struct {
|
||||||
|
IsHTTPS bool
|
||||||
|
Method string
|
||||||
|
URL string
|
||||||
|
Domain string
|
||||||
|
Host string
|
||||||
|
Data []byte
|
||||||
|
SuccessCount uint
|
||||||
|
FailCount uint
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewChecker args:
|
||||||
|
//timeout : tcp timeout milliseconds ,connect to host
|
||||||
|
//interval: recheck domain interval seconds
|
||||||
|
func NewChecker(timeout int, interval int64, blockedFile, directFile string) Checker {
|
||||||
|
ch := Checker{
|
||||||
|
data: NewConcurrentMap(),
|
||||||
|
interval: interval,
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
ch.blockedMap = ch.loadMap(blockedFile)
|
||||||
|
ch.directMap = ch.loadMap(directFile)
|
||||||
|
if !ch.blockedMap.IsEmpty() {
|
||||||
|
log.Printf("blocked file loaded , domains : %d", ch.blockedMap.Count())
|
||||||
|
}
|
||||||
|
if !ch.directMap.IsEmpty() {
|
||||||
|
log.Printf("direct file loaded , domains : %d", ch.directMap.Count())
|
||||||
|
}
|
||||||
|
ch.start()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) {
|
||||||
|
dataMap = NewConcurrentMap()
|
||||||
|
if PathExists(f) {
|
||||||
|
_contents, err := ioutil.ReadFile(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("load file err:%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(_contents), "\n") {
|
||||||
|
line = strings.Trim(line, "\r \t")
|
||||||
|
if line != "" {
|
||||||
|
dataMap.Set(line, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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.Host)
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
conn, err = ConnectHost(item.Host, c.timeout)
|
||||||
|
if err == nil {
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Millisecond))
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
item.FailCount = item.FailCount + 1
|
||||||
|
} else {
|
||||||
|
item.SuccessCount = item.SuccessCount + 1
|
||||||
|
}
|
||||||
|
c.data.Set(item.Host, item)
|
||||||
|
}
|
||||||
|
}(v.(CheckerItem))
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * time.Duration(c.interval))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
func (c *Checker) isNeedCheck(item CheckerItem) bool {
|
||||||
|
var minCount uint = 5
|
||||||
|
if (item.SuccessCount >= minCount && item.SuccessCount > item.FailCount) ||
|
||||||
|
(item.FailCount >= minCount && item.SuccessCount > item.FailCount) ||
|
||||||
|
c.domainIsInMap(item.Host, false) ||
|
||||||
|
c.domainIsInMap(item.Host, true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (c *Checker) IsBlocked(address string) (blocked bool, failN, successN uint) {
|
||||||
|
if c.domainIsInMap(address, true) {
|
||||||
|
//log.Printf("%s in blocked ? true", address)
|
||||||
|
return true, 0, 0
|
||||||
|
}
|
||||||
|
if c.domainIsInMap(address, false) {
|
||||||
|
//log.Printf("%s in direct ? true", address)
|
||||||
|
return false, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_item, ok := c.data.Get(address)
|
||||||
|
if !ok {
|
||||||
|
//log.Printf("%s not in map, blocked true", address)
|
||||||
|
return true, 0, 0
|
||||||
|
}
|
||||||
|
item := _item.(CheckerItem)
|
||||||
|
|
||||||
|
return item.FailCount >= item.SuccessCount, item.FailCount, item.SuccessCount
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
domainSlice := strings.Split(u.Hostname(), ".")
|
||||||
|
if len(domainSlice) > 1 {
|
||||||
|
subSlice := domainSlice[:len(domainSlice)-1]
|
||||||
|
topDomain := strings.Join(domainSlice[len(domainSlice)-1:], ".")
|
||||||
|
checkDomain := topDomain
|
||||||
|
for i := len(subSlice) - 1; i >= 0; i-- {
|
||||||
|
checkDomain = subSlice[i] + "." + checkDomain
|
||||||
|
if !blockedMap && c.directMap.Has(checkDomain) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if blockedMap && c.blockedMap.Has(checkDomain) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (c *Checker) Add(address string) {
|
||||||
|
if c.domainIsInMap(address, false) || c.domainIsInMap(address, true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var item CheckerItem
|
||||||
|
item = CheckerItem{
|
||||||
|
Host: address,
|
||||||
|
}
|
||||||
|
c.data.SetIfAbsent(item.Host, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicAuth struct {
|
||||||
|
data ConcurrentMap
|
||||||
|
authURL string
|
||||||
|
authOkCode int
|
||||||
|
authTimeout int
|
||||||
|
authRetry int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBasicAuth() BasicAuth {
|
||||||
|
return BasicAuth{
|
||||||
|
data: NewConcurrentMap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (ba *BasicAuth) SetAuthURL(URL string, code, timeout, retry int) {
|
||||||
|
ba.authURL = URL
|
||||||
|
ba.authOkCode = code
|
||||||
|
ba.authTimeout = timeout
|
||||||
|
ba.authRetry = retry
|
||||||
|
}
|
||||||
|
func (ba *BasicAuth) AddFromFile(file string) (n int, err error) {
|
||||||
|
_content, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userpassArr := strings.Split(strings.Replace(string(_content), "\r", "", -1), "\n")
|
||||||
|
for _, userpass := range userpassArr {
|
||||||
|
if strings.HasPrefix("#", userpass) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
u := strings.Split(strings.Trim(userpass, " "), ":")
|
||||||
|
if len(u) == 2 {
|
||||||
|
ba.data.Set(u[0], u[1])
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ba *BasicAuth) Add(userpassArr []string) (n int) {
|
||||||
|
for _, userpass := range userpassArr {
|
||||||
|
u := strings.Split(userpass, ":")
|
||||||
|
if len(u) == 2 {
|
||||||
|
ba.data.Set(u[0], u[1])
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (ba *BasicAuth) CheckUserPass(user, pass, ip, target string) (ok bool) {
|
||||||
|
|
||||||
|
return ba.Check(user+":"+pass, ip, target)
|
||||||
|
}
|
||||||
|
func (ba *BasicAuth) Check(userpass string, ip, target string) (ok bool) {
|
||||||
|
u := strings.Split(strings.Trim(userpass, " "), ":")
|
||||||
|
if len(u) == 2 {
|
||||||
|
if p, _ok := ba.data.Get(u[0]); _ok {
|
||||||
|
return p.(string) == u[1]
|
||||||
|
}
|
||||||
|
if ba.authURL != "" {
|
||||||
|
err := ba.checkFromURL(userpass, ip, target)
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log.Printf("%s", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (ba *BasicAuth) checkFromURL(userpass, ip, target string) (err error) {
|
||||||
|
u := strings.Split(strings.Trim(userpass, " "), ":")
|
||||||
|
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)
|
||||||
|
var code int
|
||||||
|
var tryCount = 0
|
||||||
|
var body []byte
|
||||||
|
for tryCount <= ba.authRetry {
|
||||||
|
body, code, err = HttpGet(URL, ba.authTimeout)
|
||||||
|
if err == nil && code == ba.authOkCode {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
err = fmt.Errorf("auth fail from url %s,resonse err:%s , %s", URL, err, ip)
|
||||||
|
} else {
|
||||||
|
if len(body) > 0 {
|
||||||
|
err = fmt.Errorf(string(body[0:100]))
|
||||||
|
} 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))
|
||||||
|
}
|
||||||
|
if err != nil && tryCount < ba.authRetry {
|
||||||
|
log.Print(err)
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
}
|
||||||
|
tryCount++
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//log.Printf("auth success from auth url, %s", ip)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ba *BasicAuth) Total() (n int) {
|
||||||
|
n = ba.data.Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequest struct {
|
||||||
|
HeadBuf []byte
|
||||||
|
conn *net.Conn
|
||||||
|
Host string
|
||||||
|
Method string
|
||||||
|
URL string
|
||||||
|
hostOrURL string
|
||||||
|
isBasicAuth bool
|
||||||
|
basicAuth *BasicAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *BasicAuth) (req HTTPRequest, err error) {
|
||||||
|
buf := make([]byte, bufSize)
|
||||||
|
len := 0
|
||||||
|
req = HTTPRequest{
|
||||||
|
conn: inConn,
|
||||||
|
}
|
||||||
|
len, err = (*inConn).Read(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
err = fmt.Errorf("http decoder read err:%s", err)
|
||||||
|
}
|
||||||
|
CloseConn(inConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.HeadBuf = buf[:len]
|
||||||
|
//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))
|
||||||
|
CloseConn(inConn)
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Method = strings.ToUpper(req.Method)
|
||||||
|
req.isBasicAuth = isBasicAuth
|
||||||
|
req.basicAuth = basicAuth
|
||||||
|
log.Printf("%s:%s", req.Method, req.hostOrURL)
|
||||||
|
|
||||||
|
if req.IsHTTPS() {
|
||||||
|
err = req.HTTPS()
|
||||||
|
} else {
|
||||||
|
err = req.HTTP()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (req *HTTPRequest) HTTP() (err error) {
|
||||||
|
if req.isBasicAuth {
|
||||||
|
err = req.BasicAuth()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.URL, err = req.getHTTPURL()
|
||||||
|
if err == nil {
|
||||||
|
var u *url.URL
|
||||||
|
u, err = url.Parse(req.URL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
_, err = fmt.Fprint(*req.conn, "HTTP/1.1 200 Connection established\r\n\r\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (req *HTTPRequest) IsHTTPS() bool {
|
||||||
|
return req.Method == "CONNECT"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *HTTPRequest) BasicAuth() (err error) {
|
||||||
|
|
||||||
|
//log.Printf("request :%s", string(b[:n]))authorization
|
||||||
|
isProxyAuthorization := false
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if authorization == "" {
|
||||||
|
authorization, err = req.getHeader("Proxy-Authorization")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint((*req.conn), "HTTP/1.1 407 Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized")
|
||||||
|
CloseConn(req.conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isProxyAuthorization = true
|
||||||
|
}
|
||||||
|
//log.Printf("Authorization:%s", authorization)
|
||||||
|
basic := strings.Fields(authorization)
|
||||||
|
if len(basic) != 2 {
|
||||||
|
err = fmt.Errorf("authorization data error,ERR:%s", authorization)
|
||||||
|
CloseConn(req.conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, err := base64.StdEncoding.DecodeString(basic[1])
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("authorization data parse error,ERR:%s", err)
|
||||||
|
CloseConn(req.conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr := strings.Split((*req.conn).RemoteAddr().String(), ":")
|
||||||
|
URL := ""
|
||||||
|
if req.IsHTTPS() {
|
||||||
|
URL = "https://" + req.Host
|
||||||
|
} else {
|
||||||
|
URL, _ = req.getHTTPURL()
|
||||||
|
}
|
||||||
|
authOk := (*req.basicAuth).Check(string(user), addr[0], URL)
|
||||||
|
//log.Printf("auth %s,%v", string(user), authOk)
|
||||||
|
if !authOk {
|
||||||
|
code := "401"
|
||||||
|
if isProxyAuthorization {
|
||||||
|
code = "407"
|
||||||
|
}
|
||||||
|
fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\n\r\nUnauthorized", code)
|
||||||
|
CloseConn(req.conn)
|
||||||
|
err = fmt.Errorf("basic auth fail")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (req *HTTPRequest) getHTTPURL() (URL string, err error) {
|
||||||
|
if !strings.HasPrefix(req.hostOrURL, "/") {
|
||||||
|
return req.hostOrURL, nil
|
||||||
|
}
|
||||||
|
_host, err := req.getHeader("host")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
URL = fmt.Sprintf("http://%s%s", _host, req.hostOrURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (req *HTTPRequest) getHeader(key string) (val string, err error) {
|
||||||
|
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], " ")
|
||||||
|
if key == k {
|
||||||
|
val = v
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *HTTPRequest) addPortIfNot() (newHost string) {
|
||||||
|
//newHost = req.Host
|
||||||
|
port := "80"
|
||||||
|
if req.IsHTTPS() {
|
||||||
|
port = "443"
|
||||||
|
}
|
||||||
|
if (!strings.HasPrefix(req.Host, "[") && strings.Index(req.Host, ":") == -1) || (strings.HasPrefix(req.Host, "[") && strings.HasSuffix(req.Host, "]")) {
|
||||||
|
//newHost = req.Host + ":" + port
|
||||||
|
//req.headBuf = []byte(strings.Replace(string(req.headBuf), req.Host, newHost, 1))
|
||||||
|
req.Host = req.Host + ":" + port
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutPool struct {
|
||||||
|
Pool ConnPool
|
||||||
|
dur int
|
||||||
|
typ string
|
||||||
|
certBytes []byte
|
||||||
|
keyBytes []byte
|
||||||
|
kcpMethod string
|
||||||
|
kcpKey string
|
||||||
|
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{
|
||||||
|
dur: dur,
|
||||||
|
typ: typ,
|
||||||
|
certBytes: certBytes,
|
||||||
|
keyBytes: keyBytes,
|
||||||
|
kcpMethod: kcpMethod,
|
||||||
|
kcpKey: kcpKey,
|
||||||
|
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) {
|
||||||
|
if op.typ == "tls" {
|
||||||
|
var _conn tls.Conn
|
||||||
|
_conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes)
|
||||||
|
if err == nil {
|
||||||
|
conn = net.Conn(&_conn)
|
||||||
|
}
|
||||||
|
} else if op.typ == "kcp" {
|
||||||
|
conn, err = ConnectKCPHost(op.address, op.kcpMethod, op.kcpKey)
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnManager() ConnManager {
|
||||||
|
cm := ConnManager{
|
||||||
|
pool: NewConcurrentMap(),
|
||||||
|
l: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
func (cm *ConnManager) Add(key, ID string, conn *net.Conn) {
|
||||||
|
cm.pool.Upsert(key, nil, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
|
||||||
|
var conns ConcurrentMap
|
||||||
|
if !exist {
|
||||||
|
conns = NewConcurrentMap()
|
||||||
|
} else {
|
||||||
|
conns = valueInMap.(ConcurrentMap)
|
||||||
|
}
|
||||||
|
if conns.Has(ID) {
|
||||||
|
v, _ := conns.Get(ID)
|
||||||
|
(*v.(*net.Conn)).Close()
|
||||||
|
}
|
||||||
|
conns.Set(ID, conn)
|
||||||
|
log.Printf("%s conn added", key)
|
||||||
|
return conns
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (cm *ConnManager) Remove(key string) {
|
||||||
|
var conns ConcurrentMap
|
||||||
|
if v, ok := cm.pool.Get(key); ok {
|
||||||
|
conns = v.(ConcurrentMap)
|
||||||
|
conns.IterCb(func(key string, v interface{}) {
|
||||||
|
CloseConn(v.(*net.Conn))
|
||||||
|
})
|
||||||
|
log.Printf("%s conns closed", key)
|
||||||
|
}
|
||||||
|
cm.pool.Remove(key)
|
||||||
|
}
|
||||||
|
func (cm *ConnManager) RemoveOne(key string, ID string) {
|
||||||
|
defer cm.l.Unlock()
|
||||||
|
cm.l.Lock()
|
||||||
|
var conns ConcurrentMap
|
||||||
|
if v, ok := cm.pool.Get(key); ok {
|
||||||
|
conns = v.(ConcurrentMap)
|
||||||
|
if conns.Has(ID) {
|
||||||
|
v, _ := conns.Get(ID)
|
||||||
|
(*v.(*net.Conn)).Close()
|
||||||
|
conns.Remove(ID)
|
||||||
|
cm.pool.Set(key, conns)
|
||||||
|
log.Printf("%s %s conn closed", key, ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (cm *ConnManager) RemoveAll() {
|
||||||
|
for _, k := range cm.pool.Keys() {
|
||||||
|
cm.Remove(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Go's `text/template` package with newline elision
|
||||||
|
|
||||||
|
This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline.
|
||||||
|
|
||||||
|
eg.
|
||||||
|
|
||||||
|
```
|
||||||
|
{{if true}}\
|
||||||
|
hello
|
||||||
|
{{end}}\
|
||||||
|
```
|
||||||
|
|
||||||
|
Will result in:
|
||||||
|
|
||||||
|
```
|
||||||
|
hello\n
|
||||||
|
```
|
||||||
|
|
||||||
|
Rather than:
|
||||||
|
|
||||||
|
```
|
||||||
|
\n
|
||||||
|
hello\n
|
||||||
|
\n
|
||||||
|
```
|
||||||
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package template implements data-driven templates for generating textual output.
|
||||||
|
|
||||||
|
To generate HTML output, see package html/template, which has the same interface
|
||||||
|
as this package but automatically secures HTML output against certain attacks.
|
||||||
|
|
||||||
|
Templates are executed by applying them to a data structure. Annotations in the
|
||||||
|
template refer to elements of the data structure (typically a field of a struct
|
||||||
|
or a key in a map) to control execution and derive values to be displayed.
|
||||||
|
Execution of the template walks the structure and sets the cursor, represented
|
||||||
|
by a period '.' and called "dot", to the value at the current location in the
|
||||||
|
structure as execution proceeds.
|
||||||
|
|
||||||
|
The input text for a template is UTF-8-encoded text in any format.
|
||||||
|
"Actions"--data evaluations or control structures--are delimited by
|
||||||
|
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||||
|
Actions may not span newlines, although comments can.
|
||||||
|
|
||||||
|
Once parsed, a template may be executed safely in parallel.
|
||||||
|
|
||||||
|
Here is a trivial example that prints "17 items are made of wool".
|
||||||
|
|
||||||
|
type Inventory struct {
|
||||||
|
Material string
|
||||||
|
Count uint
|
||||||
|
}
|
||||||
|
sweaters := Inventory{"wool", 17}
|
||||||
|
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
err = tmpl.Execute(os.Stdout, sweaters)
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
|
||||||
|
More intricate examples appear below.
|
||||||
|
|
||||||
|
Actions
|
||||||
|
|
||||||
|
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||||
|
data, defined in detail below.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// {{/* a comment */}}
|
||||||
|
// A comment; discarded. May contain newlines.
|
||||||
|
// Comments do not nest and must start and end at the
|
||||||
|
// delimiters, as shown here.
|
||||||
|
/*
|
||||||
|
|
||||||
|
{{pipeline}}
|
||||||
|
The default textual representation of the value of the pipeline
|
||||||
|
is copied to the output.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{end}}
|
||||||
|
If the value of the pipeline is empty, no output is generated;
|
||||||
|
otherwise, T1 is executed. The empty values are false, 0, any
|
||||||
|
nil pointer or interface value, and any array, slice, map, or
|
||||||
|
string of length zero.
|
||||||
|
Dot is unaffected.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
If the value of the pipeline is empty, T0 is executed;
|
||||||
|
otherwise, T1 is executed. Dot is unaffected.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||||
|
To simplify the appearance of if-else chains, the else action
|
||||||
|
of an if may include another if directly; the effect is exactly
|
||||||
|
the same as writing
|
||||||
|
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||||
|
|
||||||
|
{{range pipeline}} T1 {{end}}
|
||||||
|
The value of the pipeline must be an array, slice, map, or channel.
|
||||||
|
If the value of the pipeline has length zero, nothing is output;
|
||||||
|
otherwise, dot is set to the successive elements of the array,
|
||||||
|
slice, or map and T1 is executed. If the value is a map and the
|
||||||
|
keys are of basic type with a defined order ("comparable"), the
|
||||||
|
elements will be visited in sorted key order.
|
||||||
|
|
||||||
|
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
The value of the pipeline must be an array, slice, map, or channel.
|
||||||
|
If the value of the pipeline has length zero, dot is unaffected and
|
||||||
|
T0 is executed; otherwise, dot is set to the successive elements
|
||||||
|
of the array, slice, or map and T1 is executed.
|
||||||
|
|
||||||
|
{{template "name"}}
|
||||||
|
The template with the specified name is executed with nil data.
|
||||||
|
|
||||||
|
{{template "name" pipeline}}
|
||||||
|
The template with the specified name is executed with dot set
|
||||||
|
to the value of the pipeline.
|
||||||
|
|
||||||
|
{{with pipeline}} T1 {{end}}
|
||||||
|
If the value of the pipeline is empty, no output is generated;
|
||||||
|
otherwise, dot is set to the value of the pipeline and T1 is
|
||||||
|
executed.
|
||||||
|
|
||||||
|
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
If the value of the pipeline is empty, dot is unaffected and T0
|
||||||
|
is executed; otherwise, dot is set to the value of the pipeline
|
||||||
|
and T1 is executed.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
|
||||||
|
An argument is a simple value, denoted by one of the following.
|
||||||
|
|
||||||
|
- A boolean, string, character, integer, floating-point, imaginary
|
||||||
|
or complex constant in Go syntax. These behave like Go's untyped
|
||||||
|
constants, although raw strings may not span newlines.
|
||||||
|
- The keyword nil, representing an untyped Go nil.
|
||||||
|
- The character '.' (period):
|
||||||
|
.
|
||||||
|
The result is the value of dot.
|
||||||
|
- A variable name, which is a (possibly empty) alphanumeric string
|
||||||
|
preceded by a dollar sign, such as
|
||||||
|
$piOver2
|
||||||
|
or
|
||||||
|
$
|
||||||
|
The result is the value of the variable.
|
||||||
|
Variables are described below.
|
||||||
|
- The name of a field of the data, which must be a struct, preceded
|
||||||
|
by a period, such as
|
||||||
|
.Field
|
||||||
|
The result is the value of the field. Field invocations may be
|
||||||
|
chained:
|
||||||
|
.Field1.Field2
|
||||||
|
Fields can also be evaluated on variables, including chaining:
|
||||||
|
$x.Field1.Field2
|
||||||
|
- The name of a key of the data, which must be a map, preceded
|
||||||
|
by a period, such as
|
||||||
|
.Key
|
||||||
|
The result is the map element value indexed by the key.
|
||||||
|
Key invocations may be chained and combined with fields to any
|
||||||
|
depth:
|
||||||
|
.Field1.Key1.Field2.Key2
|
||||||
|
Although the key must be an alphanumeric identifier, unlike with
|
||||||
|
field names they do not need to start with an upper case letter.
|
||||||
|
Keys can also be evaluated on variables, including chaining:
|
||||||
|
$x.key1.key2
|
||||||
|
- The name of a niladic method of the data, preceded by a period,
|
||||||
|
such as
|
||||||
|
.Method
|
||||||
|
The result is the value of invoking the method with dot as the
|
||||||
|
receiver, dot.Method(). Such a method must have one return value (of
|
||||||
|
any type) or two return values, the second of which is an error.
|
||||||
|
If it has two and the returned error is non-nil, execution terminates
|
||||||
|
and an error is returned to the caller as the value of Execute.
|
||||||
|
Method invocations may be chained and combined with fields and keys
|
||||||
|
to any depth:
|
||||||
|
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||||
|
Methods can also be evaluated on variables, including chaining:
|
||||||
|
$x.Method1.Field
|
||||||
|
- The name of a niladic function, such as
|
||||||
|
fun
|
||||||
|
The result is the value of invoking the function, fun(). The return
|
||||||
|
types and values behave as in methods. Functions and function
|
||||||
|
names are described below.
|
||||||
|
- A parenthesized instance of one the above, for grouping. The result
|
||||||
|
may be accessed by a field or map key invocation.
|
||||||
|
print (.F1 arg1) (.F2 arg2)
|
||||||
|
(.StructValuedMethod "arg").Field
|
||||||
|
|
||||||
|
Arguments may evaluate to any type; if they are pointers the implementation
|
||||||
|
automatically indirects to the base type when required.
|
||||||
|
If an evaluation yields a function value, such as a function-valued
|
||||||
|
field of a struct, the function is not invoked automatically, but it
|
||||||
|
can be used as a truth value for an if action and the like. To invoke
|
||||||
|
it, use the call function, defined below.
|
||||||
|
|
||||||
|
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||||
|
value (argument) or a function or method call, possibly with multiple arguments:
|
||||||
|
|
||||||
|
Argument
|
||||||
|
The result is the value of evaluating the argument.
|
||||||
|
.Method [Argument...]
|
||||||
|
The method can be alone or the last element of a chain but,
|
||||||
|
unlike methods in the middle of a chain, it can take arguments.
|
||||||
|
The result is the value of calling the method with the
|
||||||
|
arguments:
|
||||||
|
dot.Method(Argument1, etc.)
|
||||||
|
functionName [Argument...]
|
||||||
|
The result is the value of calling the function associated
|
||||||
|
with the name:
|
||||||
|
function(Argument1, etc.)
|
||||||
|
Functions and function names are described below.
|
||||||
|
|
||||||
|
Pipelines
|
||||||
|
|
||||||
|
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||||
|
characters '|'. In a chained pipeline, the result of the each command is
|
||||||
|
passed as the last argument of the following command. The output of the final
|
||||||
|
command in the pipeline is the value of the pipeline.
|
||||||
|
|
||||||
|
The output of a command will be either one value or two values, the second of
|
||||||
|
which has type error. If that second value is present and evaluates to
|
||||||
|
non-nil, execution terminates and the error is returned to the caller of
|
||||||
|
Execute.
|
||||||
|
|
||||||
|
Variables
|
||||||
|
|
||||||
|
A pipeline inside an action may initialize a variable to capture the result.
|
||||||
|
The initialization has syntax
|
||||||
|
|
||||||
|
$variable := pipeline
|
||||||
|
|
||||||
|
where $variable is the name of the variable. An action that declares a
|
||||||
|
variable produces no output.
|
||||||
|
|
||||||
|
If a "range" action initializes a variable, the variable is set to the
|
||||||
|
successive elements of the iteration. Also, a "range" may declare two
|
||||||
|
variables, separated by a comma:
|
||||||
|
|
||||||
|
range $index, $element := pipeline
|
||||||
|
|
||||||
|
in which case $index and $element are set to the successive values of the
|
||||||
|
array/slice index or map key and element, respectively. Note that if there is
|
||||||
|
only one variable, it is assigned the element; this is opposite to the
|
||||||
|
convention in Go range clauses.
|
||||||
|
|
||||||
|
A variable's scope extends to the "end" action of the control structure ("if",
|
||||||
|
"with", or "range") in which it is declared, or to the end of the template if
|
||||||
|
there is no such control structure. A template invocation does not inherit
|
||||||
|
variables from the point of its invocation.
|
||||||
|
|
||||||
|
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||||
|
to the starting value of dot.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
Here are some example one-line templates demonstrating pipelines and variables.
|
||||||
|
All produce the quoted word "output":
|
||||||
|
|
||||||
|
{{"\"output\""}}
|
||||||
|
A string constant.
|
||||||
|
{{`"output"`}}
|
||||||
|
A raw string constant.
|
||||||
|
{{printf "%q" "output"}}
|
||||||
|
A function call.
|
||||||
|
{{"output" | printf "%q"}}
|
||||||
|
A function call whose final argument comes from the previous
|
||||||
|
command.
|
||||||
|
{{printf "%q" (print "out" "put")}}
|
||||||
|
A parenthesized argument.
|
||||||
|
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||||
|
A more elaborate call.
|
||||||
|
{{"output" | printf "%s" | printf "%q"}}
|
||||||
|
A longer chain.
|
||||||
|
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||||
|
A with action using dot.
|
||||||
|
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||||
|
A with action that creates and uses a variable.
|
||||||
|
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||||
|
A with action that uses the variable in another action.
|
||||||
|
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||||
|
The same, but pipelined.
|
||||||
|
|
||||||
|
Functions
|
||||||
|
|
||||||
|
During execution functions are found in two function maps: first in the
|
||||||
|
template, then in the global function map. By default, no functions are defined
|
||||||
|
in the template but the Funcs method can be used to add them.
|
||||||
|
|
||||||
|
Predefined global functions are named as follows.
|
||||||
|
|
||||||
|
and
|
||||||
|
Returns the boolean AND of its arguments by returning the
|
||||||
|
first empty argument or the last argument, that is,
|
||||||
|
"and x y" behaves as "if x then y else x". All the
|
||||||
|
arguments are evaluated.
|
||||||
|
call
|
||||||
|
Returns the result of calling the first argument, which
|
||||||
|
must be a function, with the remaining arguments as parameters.
|
||||||
|
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||||
|
Y is a func-valued field, map entry, or the like.
|
||||||
|
The first argument must be the result of an evaluation
|
||||||
|
that yields a value of function type (as distinct from
|
||||||
|
a predefined function such as print). The function must
|
||||||
|
return either one or two result values, the second of which
|
||||||
|
is of type error. If the arguments don't match the function
|
||||||
|
or the returned error value is non-nil, execution stops.
|
||||||
|
html
|
||||||
|
Returns the escaped HTML equivalent of the textual
|
||||||
|
representation of its arguments.
|
||||||
|
index
|
||||||
|
Returns the result of indexing its first argument by the
|
||||||
|
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||||
|
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||||
|
js
|
||||||
|
Returns the escaped JavaScript equivalent of the textual
|
||||||
|
representation of its arguments.
|
||||||
|
len
|
||||||
|
Returns the integer length of its argument.
|
||||||
|
not
|
||||||
|
Returns the boolean negation of its single argument.
|
||||||
|
or
|
||||||
|
Returns the boolean OR of its arguments by returning the
|
||||||
|
first non-empty argument or the last argument, that is,
|
||||||
|
"or x y" behaves as "if x then x else y". All the
|
||||||
|
arguments are evaluated.
|
||||||
|
print
|
||||||
|
An alias for fmt.Sprint
|
||||||
|
printf
|
||||||
|
An alias for fmt.Sprintf
|
||||||
|
println
|
||||||
|
An alias for fmt.Sprintln
|
||||||
|
urlquery
|
||||||
|
Returns the escaped value of the textual representation of
|
||||||
|
its arguments in a form suitable for embedding in a URL query.
|
||||||
|
|
||||||
|
The boolean functions take any zero value to be false and a non-zero
|
||||||
|
value to be true.
|
||||||
|
|
||||||
|
There is also a set of binary comparison operators defined as
|
||||||
|
functions:
|
||||||
|
|
||||||
|
eq
|
||||||
|
Returns the boolean truth of arg1 == arg2
|
||||||
|
ne
|
||||||
|
Returns the boolean truth of arg1 != arg2
|
||||||
|
lt
|
||||||
|
Returns the boolean truth of arg1 < arg2
|
||||||
|
le
|
||||||
|
Returns the boolean truth of arg1 <= arg2
|
||||||
|
gt
|
||||||
|
Returns the boolean truth of arg1 > arg2
|
||||||
|
ge
|
||||||
|
Returns the boolean truth of arg1 >= arg2
|
||||||
|
|
||||||
|
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||||
|
arguments and compares the second and subsequent to the first,
|
||||||
|
returning in effect
|
||||||
|
|
||||||
|
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||||
|
|
||||||
|
(Unlike with || in Go, however, eq is a function call and all the
|
||||||
|
arguments will be evaluated.)
|
||||||
|
|
||||||
|
The comparison functions work on basic types only (or named basic
|
||||||
|
types, such as "type Celsius float32"). They implement the Go rules
|
||||||
|
for comparison of values, except that size and exact type are
|
||||||
|
ignored, so any integer value, signed or unsigned, may be compared
|
||||||
|
with any other integer value. (The arithmetic value is compared,
|
||||||
|
not the bit pattern, so all negative integers are less than all
|
||||||
|
unsigned integers.) However, as usual, one may not compare an int
|
||||||
|
with a float32 and so on.
|
||||||
|
|
||||||
|
Associated templates
|
||||||
|
|
||||||
|
Each template is named by a string specified when it is created. Also, each
|
||||||
|
template is associated with zero or more other templates that it may invoke by
|
||||||
|
name; such associations are transitive and form a name space of templates.
|
||||||
|
|
||||||
|
A template may use a template invocation to instantiate another associated
|
||||||
|
template; see the explanation of the "template" action above. The name must be
|
||||||
|
that of a template associated with the template that contains the invocation.
|
||||||
|
|
||||||
|
Nested template definitions
|
||||||
|
|
||||||
|
When parsing a template, another template may be defined and associated with the
|
||||||
|
template being parsed. Template definitions must appear at the top level of the
|
||||||
|
template, much like global variables in a Go program.
|
||||||
|
|
||||||
|
The syntax of such definitions is to surround each template declaration with a
|
||||||
|
"define" and "end" action.
|
||||||
|
|
||||||
|
The define action names the template being created by providing a string
|
||||||
|
constant. Here is a simple example:
|
||||||
|
|
||||||
|
`{{define "T1"}}ONE{{end}}
|
||||||
|
{{define "T2"}}TWO{{end}}
|
||||||
|
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||||
|
{{template "T3"}}`
|
||||||
|
|
||||||
|
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||||
|
when it is executed. Finally it invokes T3. If executed this template will
|
||||||
|
produce the text
|
||||||
|
|
||||||
|
ONE TWO
|
||||||
|
|
||||||
|
By construction, a template may reside in only one association. If it's
|
||||||
|
necessary to have a template addressable from multiple associations, the
|
||||||
|
template definition must be parsed multiple times to create distinct *Template
|
||||||
|
values, or must be copied with the Clone or AddParseTree method.
|
||||||
|
|
||||||
|
Parse may be called multiple times to assemble the various associated templates;
|
||||||
|
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||||
|
related templates stored in files.
|
||||||
|
|
||||||
|
A template may be executed directly or through ExecuteTemplate, which executes
|
||||||
|
an associated template identified by name. To invoke our example above, we
|
||||||
|
might write,
|
||||||
|
|
||||||
|
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execution failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
or to invoke a particular template explicitly by name,
|
||||||
|
|
||||||
|
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execution failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package template
|
||||||
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
Normal file
@ -0,0 +1,845 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// state represents the state of an execution. It's not part of the
|
||||||
|
// template so that multiple executions of the same template
|
||||||
|
// can execute in parallel.
|
||||||
|
type state struct {
|
||||||
|
tmpl *Template
|
||||||
|
wr io.Writer
|
||||||
|
node parse.Node // current node, for errors
|
||||||
|
vars []variable // push-down stack of variable values.
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||||
|
type variable struct {
|
||||||
|
name string
|
||||||
|
value reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// push pushes a new variable on the stack.
|
||||||
|
func (s *state) push(name string, value reflect.Value) {
|
||||||
|
s.vars = append(s.vars, variable{name, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark returns the length of the variable stack.
|
||||||
|
func (s *state) mark() int {
|
||||||
|
return len(s.vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop pops the variable stack up to the mark.
|
||||||
|
func (s *state) pop(mark int) {
|
||||||
|
s.vars = s.vars[0:mark]
|
||||||
|
}
|
||||||
|
|
||||||
|
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||||
|
func (s *state) setVar(n int, value reflect.Value) {
|
||||||
|
s.vars[len(s.vars)-n].value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// varValue returns the value of the named variable.
|
||||||
|
func (s *state) varValue(name string) reflect.Value {
|
||||||
|
for i := s.mark() - 1; i >= 0; i-- {
|
||||||
|
if s.vars[i].name == name {
|
||||||
|
return s.vars[i].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.errorf("undefined variable: %s", name)
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
var zero reflect.Value
|
||||||
|
|
||||||
|
// at marks the state to be on node n, for error reporting.
|
||||||
|
func (s *state) at(node parse.Node) {
|
||||||
|
s.node = node
|
||||||
|
}
|
||||||
|
|
||||||
|
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||||
|
// so it can be used safely inside a Printf format string.
|
||||||
|
func doublePercent(str string) string {
|
||||||
|
if strings.Contains(str, "%") {
|
||||||
|
str = strings.Replace(str, "%", "%%", -1)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf formats the error and terminates processing.
|
||||||
|
func (s *state) errorf(format string, args ...interface{}) {
|
||||||
|
name := doublePercent(s.tmpl.Name())
|
||||||
|
if s.node == nil {
|
||||||
|
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||||
|
} else {
|
||||||
|
location, context := s.tmpl.ErrorContext(s.node)
|
||||||
|
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// errRecover is the handler that turns panics into returns from the top
|
||||||
|
// level of Parse.
|
||||||
|
func errRecover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
switch err := e.(type) {
|
||||||
|
case runtime.Error:
|
||||||
|
panic(e)
|
||||||
|
case error:
|
||||||
|
*errp = err
|
||||||
|
default:
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteTemplate applies the template associated with t that has the given name
|
||||||
|
// to the specified data object and writes the output to wr.
|
||||||
|
// If an error occurs executing the template or writing its output,
|
||||||
|
// execution stops, but partial results may already have been written to
|
||||||
|
// the output writer.
|
||||||
|
// A template may be executed safely in parallel.
|
||||||
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||||
|
tmpl := t.tmpl[name]
|
||||||
|
if tmpl == nil {
|
||||||
|
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||||
|
}
|
||||||
|
return tmpl.Execute(wr, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute applies a parsed template to the specified data object,
|
||||||
|
// and writes the output to wr.
|
||||||
|
// If an error occurs executing the template or writing its output,
|
||||||
|
// execution stops, but partial results may already have been written to
|
||||||
|
// the output writer.
|
||||||
|
// A template may be executed safely in parallel.
|
||||||
|
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||||
|
defer errRecover(&err)
|
||||||
|
value := reflect.ValueOf(data)
|
||||||
|
state := &state{
|
||||||
|
tmpl: t,
|
||||||
|
wr: wr,
|
||||||
|
vars: []variable{{"$", value}},
|
||||||
|
}
|
||||||
|
t.init()
|
||||||
|
if t.Tree == nil || t.Root == nil {
|
||||||
|
var b bytes.Buffer
|
||||||
|
for name, tmpl := range t.tmpl {
|
||||||
|
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "%q", name)
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
if b.Len() > 0 {
|
||||||
|
s = "; defined templates are: " + b.String()
|
||||||
|
}
|
||||||
|
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
||||||
|
}
|
||||||
|
state.walk(value, t.Root)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk functions step through the major pieces of the template structure,
|
||||||
|
// generating output as they go.
|
||||||
|
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||||
|
s.at(node)
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *parse.ActionNode:
|
||||||
|
// Do not pop variables so they persist until next end.
|
||||||
|
// Also, if the action declares variables, don't print the result.
|
||||||
|
val := s.evalPipeline(dot, node.Pipe)
|
||||||
|
if len(node.Pipe.Decl) == 0 {
|
||||||
|
s.printValue(node, val)
|
||||||
|
}
|
||||||
|
case *parse.IfNode:
|
||||||
|
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||||
|
case *parse.ListNode:
|
||||||
|
for _, node := range node.Nodes {
|
||||||
|
s.walk(dot, node)
|
||||||
|
}
|
||||||
|
case *parse.RangeNode:
|
||||||
|
s.walkRange(dot, node)
|
||||||
|
case *parse.TemplateNode:
|
||||||
|
s.walkTemplate(dot, node)
|
||||||
|
case *parse.TextNode:
|
||||||
|
if _, err := s.wr.Write(node.Text); err != nil {
|
||||||
|
s.errorf("%s", err)
|
||||||
|
}
|
||||||
|
case *parse.WithNode:
|
||||||
|
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||||
|
default:
|
||||||
|
s.errorf("unknown node: %s", node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||||
|
// are identical in behavior except that 'with' sets dot.
|
||||||
|
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||||
|
defer s.pop(s.mark())
|
||||||
|
val := s.evalPipeline(dot, pipe)
|
||||||
|
truth, ok := isTrue(val)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("if/with can't use %v", val)
|
||||||
|
}
|
||||||
|
if truth {
|
||||||
|
if typ == parse.NodeWith {
|
||||||
|
s.walk(val, list)
|
||||||
|
} else {
|
||||||
|
s.walk(dot, list)
|
||||||
|
}
|
||||||
|
} else if elseList != nil {
|
||||||
|
s.walk(dot, elseList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||||
|
// and whether the value has a meaningful truth value.
|
||||||
|
func isTrue(val reflect.Value) (truth, ok bool) {
|
||||||
|
if !val.IsValid() {
|
||||||
|
// Something like var x interface{}, never set. It's a form of nil.
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
truth = val.Len() > 0
|
||||||
|
case reflect.Bool:
|
||||||
|
truth = val.Bool()
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
truth = val.Complex() != 0
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||||
|
truth = !val.IsNil()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
truth = val.Int() != 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
truth = val.Float() != 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
truth = val.Uint() != 0
|
||||||
|
case reflect.Struct:
|
||||||
|
truth = true // Struct values are always true.
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return truth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
|
s.at(r)
|
||||||
|
defer s.pop(s.mark())
|
||||||
|
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||||
|
// mark top of stack before any variables in the body are pushed.
|
||||||
|
mark := s.mark()
|
||||||
|
oneIteration := func(index, elem reflect.Value) {
|
||||||
|
// Set top var (lexically the second if there are two) to the element.
|
||||||
|
if len(r.Pipe.Decl) > 0 {
|
||||||
|
s.setVar(1, elem)
|
||||||
|
}
|
||||||
|
// Set next var (lexically the first if there are two) to the index.
|
||||||
|
if len(r.Pipe.Decl) > 1 {
|
||||||
|
s.setVar(2, index)
|
||||||
|
}
|
||||||
|
s.walk(elem, r.List)
|
||||||
|
s.pop(mark)
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Map:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, key := range sortKeys(val.MapKeys()) {
|
||||||
|
oneIteration(key, val.MapIndex(key))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Chan:
|
||||||
|
if val.IsNil() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for ; ; i++ {
|
||||||
|
elem, ok := val.Recv()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
oneIteration(reflect.ValueOf(i), elem)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Invalid:
|
||||||
|
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||||
|
default:
|
||||||
|
s.errorf("range can't iterate over %v", val)
|
||||||
|
}
|
||||||
|
if r.ElseList != nil {
|
||||||
|
s.walk(dot, r.ElseList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||||
|
s.at(t)
|
||||||
|
tmpl := s.tmpl.tmpl[t.Name]
|
||||||
|
if tmpl == nil {
|
||||||
|
s.errorf("template %q not defined", t.Name)
|
||||||
|
}
|
||||||
|
// Variables declared by the pipeline persist.
|
||||||
|
dot = s.evalPipeline(dot, t.Pipe)
|
||||||
|
newState := *s
|
||||||
|
newState.tmpl = tmpl
|
||||||
|
// No dynamic scoping: template invocations inherit no variables.
|
||||||
|
newState.vars = []variable{{"$", dot}}
|
||||||
|
newState.walk(dot, tmpl.Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||||
|
// values from the data structure by examining fields, calling methods, and so on.
|
||||||
|
// The printing of those values happens only through walk functions.
|
||||||
|
|
||||||
|
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||||
|
// pipeline has a variable declaration, the variable will be pushed on the
|
||||||
|
// stack. Callers should therefore pop the stack after they are finished
|
||||||
|
// executing commands depending on the pipeline value.
|
||||||
|
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||||
|
if pipe == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.at(pipe)
|
||||||
|
for _, cmd := range pipe.Cmds {
|
||||||
|
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||||
|
// If the object has type interface{}, dig down one level to the thing inside.
|
||||||
|
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||||
|
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, variable := range pipe.Decl {
|
||||||
|
s.push(variable.Ident[0], value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||||
|
if len(args) > 1 || final.IsValid() {
|
||||||
|
s.errorf("can't give argument to non-function %s", args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||||
|
firstWord := cmd.Args[0]
|
||||||
|
switch n := firstWord.(type) {
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||||
|
case *parse.ChainNode:
|
||||||
|
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
// Must be a function.
|
||||||
|
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||||
|
return s.evalPipeline(dot, n)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||||
|
}
|
||||||
|
s.at(firstWord)
|
||||||
|
s.notAFunction(cmd.Args, final)
|
||||||
|
switch word := firstWord.(type) {
|
||||||
|
case *parse.BoolNode:
|
||||||
|
return reflect.ValueOf(word.True)
|
||||||
|
case *parse.DotNode:
|
||||||
|
return dot
|
||||||
|
case *parse.NilNode:
|
||||||
|
s.errorf("nil is not a command")
|
||||||
|
case *parse.NumberNode:
|
||||||
|
return s.idealConstant(word)
|
||||||
|
case *parse.StringNode:
|
||||||
|
return reflect.ValueOf(word.Text)
|
||||||
|
}
|
||||||
|
s.errorf("can't evaluate command %q", firstWord)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// idealConstant is called to return the value of a number in a context where
|
||||||
|
// we don't know the type. In that case, the syntax of the number tells us
|
||||||
|
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||||
|
// a uint ideal constant in this situation - the value must be of int type.
|
||||||
|
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||||
|
// These are ideal constants but we don't know the type
|
||||||
|
// and we have no context. (If it was a method argument,
|
||||||
|
// we'd know what we need.) The syntax guides us to some extent.
|
||||||
|
s.at(constant)
|
||||||
|
switch {
|
||||||
|
case constant.IsComplex:
|
||||||
|
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||||
|
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||||
|
return reflect.ValueOf(constant.Float64)
|
||||||
|
case constant.IsInt:
|
||||||
|
n := int(constant.Int64)
|
||||||
|
if int64(n) != constant.Int64 {
|
||||||
|
s.errorf("%s overflows int", constant.Text)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(n)
|
||||||
|
case constant.IsUint:
|
||||||
|
s.errorf("%s overflows int", constant.Text)
|
||||||
|
}
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexConstant(s string) bool {
|
||||||
|
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(field)
|
||||||
|
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(chain)
|
||||||
|
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||||
|
pipe := s.evalArg(dot, nil, chain.Node)
|
||||||
|
if len(chain.Field) == 0 {
|
||||||
|
s.errorf("internal error: no fields in evalChainNode")
|
||||||
|
}
|
||||||
|
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||||
|
s.at(variable)
|
||||||
|
value := s.varValue(variable.Ident[0])
|
||||||
|
if len(variable.Ident) == 1 {
|
||||||
|
s.notAFunction(args, final)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||||
|
// dot is the environment in which to evaluate arguments, while
|
||||||
|
// receiver is the value being walked along the chain.
|
||||||
|
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
n := len(ident)
|
||||||
|
for i := 0; i < n-1; i++ {
|
||||||
|
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
||||||
|
}
|
||||||
|
// Now if it's a method, it gets the arguments.
|
||||||
|
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(node)
|
||||||
|
name := node.Ident
|
||||||
|
function, ok := findFunction(name, s.tmpl)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("%q is not a defined function", name)
|
||||||
|
}
|
||||||
|
return s.evalCall(dot, function, cmd, name, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||||
|
// The 'final' argument represents the return value from the preceding
|
||||||
|
// value of the pipeline, if any.
|
||||||
|
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||||
|
if !receiver.IsValid() {
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
typ := receiver.Type()
|
||||||
|
receiver, _ = indirect(receiver)
|
||||||
|
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||||
|
// we see all methods of T and *T.
|
||||||
|
ptr := receiver
|
||||||
|
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
||||||
|
ptr = ptr.Addr()
|
||||||
|
}
|
||||||
|
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||||
|
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||||
|
}
|
||||||
|
hasArgs := len(args) > 1 || final.IsValid()
|
||||||
|
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
||||||
|
receiver, isNil := indirect(receiver)
|
||||||
|
if isNil {
|
||||||
|
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||||
|
}
|
||||||
|
switch receiver.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||||
|
if ok {
|
||||||
|
field := receiver.FieldByIndex(tField.Index)
|
||||||
|
if tField.PkgPath != "" { // field is unexported
|
||||||
|
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||||
|
}
|
||||||
|
// If it's a function, we must call it.
|
||||||
|
if hasArgs {
|
||||||
|
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
// If it's a map, attempt to use the field name as a key.
|
||||||
|
nameVal := reflect.ValueOf(fieldName)
|
||||||
|
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||||
|
if hasArgs {
|
||||||
|
s.errorf("%s is not a method but has arguments", fieldName)
|
||||||
|
}
|
||||||
|
return receiver.MapIndex(nameVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||||
|
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||||
|
// as the function itself.
|
||||||
|
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
if args != nil {
|
||||||
|
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||||
|
}
|
||||||
|
typ := fun.Type()
|
||||||
|
numIn := len(args)
|
||||||
|
if final.IsValid() {
|
||||||
|
numIn++
|
||||||
|
}
|
||||||
|
numFixed := len(args)
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||||
|
if numIn < numFixed {
|
||||||
|
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||||
|
}
|
||||||
|
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||||
|
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||||
|
}
|
||||||
|
if !goodFunc(typ) {
|
||||||
|
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||||
|
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||||
|
}
|
||||||
|
// Build the arg list.
|
||||||
|
argv := make([]reflect.Value, numIn)
|
||||||
|
// Args must be evaluated. Fixed args first.
|
||||||
|
i := 0
|
||||||
|
for ; i < numFixed && i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||||
|
}
|
||||||
|
// Now the ... args.
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||||
|
for ; i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(dot, argType, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add final value if necessary.
|
||||||
|
if final.IsValid() {
|
||||||
|
t := typ.In(typ.NumIn() - 1)
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
argv[i] = s.validateType(final, t)
|
||||||
|
}
|
||||||
|
result := fun.Call(argv)
|
||||||
|
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||||
|
if len(result) == 2 && !result[1].IsNil() {
|
||||||
|
s.at(node)
|
||||||
|
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||||
|
}
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||||
|
func canBeNil(typ reflect.Type) bool {
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateType guarantees that the value is valid and assignable to the type.
|
||||||
|
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||||
|
if !value.IsValid() {
|
||||||
|
if typ == nil || canBeNil(typ) {
|
||||||
|
// An untyped nil interface{}. Accept as a proper nil value.
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
s.errorf("invalid value; expected %s", typ)
|
||||||
|
}
|
||||||
|
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||||
|
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||||
|
value = value.Elem()
|
||||||
|
if value.Type().AssignableTo(typ) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
}
|
||||||
|
// Does one dereference or indirection work? We could do more, as we
|
||||||
|
// do with method receivers, but that gets messy and method receivers
|
||||||
|
// are much more constrained, so it makes more sense there than here.
|
||||||
|
// Besides, one is almost always all you need.
|
||||||
|
switch {
|
||||||
|
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||||
|
value = value.Elem()
|
||||||
|
if !value.IsValid() {
|
||||||
|
s.errorf("dereference of nil pointer of type %s", typ)
|
||||||
|
}
|
||||||
|
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||||
|
value = value.Addr()
|
||||||
|
default:
|
||||||
|
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
switch arg := n.(type) {
|
||||||
|
case *parse.DotNode:
|
||||||
|
return s.validateType(dot, typ)
|
||||||
|
case *parse.NilNode:
|
||||||
|
if canBeNil(typ) {
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
s.errorf("cannot assign nil to %s", typ)
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
return s.evalFunction(dot, arg, arg, nil, zero)
|
||||||
|
case *parse.ChainNode:
|
||||||
|
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
||||||
|
}
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return s.evalBool(typ, n)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return s.evalComplex(typ, n)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return s.evalFloat(typ, n)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return s.evalInteger(typ, n)
|
||||||
|
case reflect.Interface:
|
||||||
|
if typ.NumMethod() == 0 {
|
||||||
|
return s.evalEmptyInterface(dot, n)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
return s.evalString(typ, n)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return s.evalUnsignedInteger(typ, n)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.BoolNode); ok {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetBool(n.True)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected bool; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.StringNode); ok {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetString(n.Text)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected string; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetInt(n.Int64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected integer; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetUint(n.Uint64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected unsigned integer; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetFloat(n.Float64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected float; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetComplex(n.Complex128)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected complex; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *parse.BoolNode:
|
||||||
|
return reflect.ValueOf(n.True)
|
||||||
|
case *parse.DotNode:
|
||||||
|
return dot
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.evalFieldNode(dot, n, nil, zero)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
return s.evalFunction(dot, n, n, nil, zero)
|
||||||
|
case *parse.NilNode:
|
||||||
|
// NilNode is handled in evalArg, the only place that calls here.
|
||||||
|
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||||
|
case *parse.NumberNode:
|
||||||
|
return s.idealConstant(n)
|
||||||
|
case *parse.StringNode:
|
||||||
|
return reflect.ValueOf(n.Text)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.evalVariableNode(dot, n, nil, zero)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
return s.evalPipeline(dot, n)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||||
|
// We indirect through pointers and empty interfaces (only) because
|
||||||
|
// non-empty interfaces have methods we might need.
|
||||||
|
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||||
|
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||||
|
if v.IsNil() {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printValue writes the textual representation of the value to the output of
|
||||||
|
// the template.
|
||||||
|
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||||
|
s.at(n)
|
||||||
|
iface, ok := printableValue(v)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("can't print %s of type %s", n, v.Type())
|
||||||
|
}
|
||||||
|
fmt.Fprint(s.wr, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printableValue returns the, possibly indirected, interface value inside v that
|
||||||
|
// is best for a call to formatted printer.
|
||||||
|
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||||
|
}
|
||||||
|
if !v.IsValid() {
|
||||||
|
return "<no value>", true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||||
|
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||||
|
v = v.Addr()
|
||||||
|
} else {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.Interface(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types to help sort the keys in a map for reproducible output.
|
||||||
|
|
||||||
|
type rvs []reflect.Value
|
||||||
|
|
||||||
|
func (x rvs) Len() int { return len(x) }
|
||||||
|
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
type rvInts struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
||||||
|
|
||||||
|
type rvUints struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
||||||
|
|
||||||
|
type rvFloats struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
||||||
|
|
||||||
|
type rvStrings struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
||||||
|
|
||||||
|
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||||
|
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||||
|
if len(v) <= 1 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
switch v[0].Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
sort.Sort(rvFloats{v})
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
sort.Sort(rvInts{v})
|
||||||
|
case reflect.String:
|
||||||
|
sort.Sort(rvStrings{v})
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
sort.Sort(rvUints{v})
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
Normal file
@ -0,0 +1,598 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||||
|
// Each function must have either a single return value, or two return values of
|
||||||
|
// which the second has type error. In that case, if the second (error)
|
||||||
|
// return value evaluates to non-nil during execution, execution terminates and
|
||||||
|
// Execute returns that error.
|
||||||
|
type FuncMap map[string]interface{}
|
||||||
|
|
||||||
|
var builtins = FuncMap{
|
||||||
|
"and": and,
|
||||||
|
"call": call,
|
||||||
|
"html": HTMLEscaper,
|
||||||
|
"index": index,
|
||||||
|
"js": JSEscaper,
|
||||||
|
"len": length,
|
||||||
|
"not": not,
|
||||||
|
"or": or,
|
||||||
|
"print": fmt.Sprint,
|
||||||
|
"printf": fmt.Sprintf,
|
||||||
|
"println": fmt.Sprintln,
|
||||||
|
"urlquery": URLQueryEscaper,
|
||||||
|
|
||||||
|
// Comparisons
|
||||||
|
"eq": eq, // ==
|
||||||
|
"ge": ge, // >=
|
||||||
|
"gt": gt, // >
|
||||||
|
"le": le, // <=
|
||||||
|
"lt": lt, // <
|
||||||
|
"ne": ne, // !=
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtinFuncs = createValueFuncs(builtins)
|
||||||
|
|
||||||
|
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||||
|
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||||
|
m := make(map[string]reflect.Value)
|
||||||
|
addValueFuncs(m, funcMap)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||||
|
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||||
|
for name, fn := range in {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
if v.Kind() != reflect.Func {
|
||||||
|
panic("value for " + name + " not a function")
|
||||||
|
}
|
||||||
|
if !goodFunc(v.Type()) {
|
||||||
|
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||||
|
}
|
||||||
|
out[name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||||
|
// call addValueFuncs first.
|
||||||
|
func addFuncs(out, in FuncMap) {
|
||||||
|
for name, fn := range in {
|
||||||
|
out[name] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// goodFunc checks that the function or method has the right result signature.
|
||||||
|
func goodFunc(typ reflect.Type) bool {
|
||||||
|
// We allow functions with 1 result or 2 results where the second is an error.
|
||||||
|
switch {
|
||||||
|
case typ.NumOut() == 1:
|
||||||
|
return true
|
||||||
|
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// findFunction looks for a function in the template, and global map.
|
||||||
|
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||||
|
if tmpl != nil && tmpl.common != nil {
|
||||||
|
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
return reflect.Value{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexing.
|
||||||
|
|
||||||
|
// index returns the result of indexing its first argument by the following
|
||||||
|
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||||
|
// indexed item must be a map, slice, or array.
|
||||||
|
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
||||||
|
v := reflect.ValueOf(item)
|
||||||
|
for _, i := range indices {
|
||||||
|
index := reflect.ValueOf(i)
|
||||||
|
var isNil bool
|
||||||
|
if v, isNil = indirect(v); isNil {
|
||||||
|
return nil, fmt.Errorf("index of nil pointer")
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.String:
|
||||||
|
var x int64
|
||||||
|
switch index.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
x = index.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
x = int64(index.Uint())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||||
|
}
|
||||||
|
if x < 0 || x >= int64(v.Len()) {
|
||||||
|
return nil, fmt.Errorf("index out of range: %d", x)
|
||||||
|
}
|
||||||
|
v = v.Index(int(x))
|
||||||
|
case reflect.Map:
|
||||||
|
if !index.IsValid() {
|
||||||
|
index = reflect.Zero(v.Type().Key())
|
||||||
|
}
|
||||||
|
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||||
|
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
||||||
|
}
|
||||||
|
if x := v.MapIndex(index); x.IsValid() {
|
||||||
|
v = x
|
||||||
|
} else {
|
||||||
|
v = reflect.Zero(v.Type().Elem())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length
|
||||||
|
|
||||||
|
// length returns the length of the item, with an error if it has no defined length.
|
||||||
|
func length(item interface{}) (int, error) {
|
||||||
|
v, isNil := indirect(reflect.ValueOf(item))
|
||||||
|
if isNil {
|
||||||
|
return 0, fmt.Errorf("len of nil pointer")
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function invocation
|
||||||
|
|
||||||
|
// call returns the result of evaluating the first argument as a function.
|
||||||
|
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||||
|
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
typ := v.Type()
|
||||||
|
if typ.Kind() != reflect.Func {
|
||||||
|
return nil, fmt.Errorf("non-function of type %s", typ)
|
||||||
|
}
|
||||||
|
if !goodFunc(typ) {
|
||||||
|
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||||
|
}
|
||||||
|
numIn := typ.NumIn()
|
||||||
|
var dddType reflect.Type
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
if len(args) < numIn-1 {
|
||||||
|
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||||
|
}
|
||||||
|
dddType = typ.In(numIn - 1).Elem()
|
||||||
|
} else {
|
||||||
|
if len(args) != numIn {
|
||||||
|
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argv := make([]reflect.Value, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
value := reflect.ValueOf(arg)
|
||||||
|
// Compute the expected type. Clumsy because of variadics.
|
||||||
|
var argType reflect.Type
|
||||||
|
if !typ.IsVariadic() || i < numIn-1 {
|
||||||
|
argType = typ.In(i)
|
||||||
|
} else {
|
||||||
|
argType = dddType
|
||||||
|
}
|
||||||
|
if !value.IsValid() && canBeNil(argType) {
|
||||||
|
value = reflect.Zero(argType)
|
||||||
|
}
|
||||||
|
if !value.Type().AssignableTo(argType) {
|
||||||
|
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
||||||
|
}
|
||||||
|
argv[i] = value
|
||||||
|
}
|
||||||
|
result := v.Call(argv)
|
||||||
|
if len(result) == 2 && !result[1].IsNil() {
|
||||||
|
return result[0].Interface(), result[1].Interface().(error)
|
||||||
|
}
|
||||||
|
return result[0].Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean logic.
|
||||||
|
|
||||||
|
func truth(a interface{}) bool {
|
||||||
|
t, _ := isTrue(reflect.ValueOf(a))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// and computes the Boolean AND of its arguments, returning
|
||||||
|
// the first false argument it encounters, or the last argument.
|
||||||
|
func and(arg0 interface{}, args ...interface{}) interface{} {
|
||||||
|
if !truth(arg0) {
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
arg0 = args[i]
|
||||||
|
if !truth(arg0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
|
||||||
|
// or computes the Boolean OR of its arguments, returning
|
||||||
|
// the first true argument it encounters, or the last argument.
|
||||||
|
func or(arg0 interface{}, args ...interface{}) interface{} {
|
||||||
|
if truth(arg0) {
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
arg0 = args[i]
|
||||||
|
if truth(arg0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
|
||||||
|
// not returns the Boolean negation of its argument.
|
||||||
|
func not(arg interface{}) (truth bool) {
|
||||||
|
truth, _ = isTrue(reflect.ValueOf(arg))
|
||||||
|
return !truth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison.
|
||||||
|
|
||||||
|
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadComparisonType = errors.New("invalid type for comparison")
|
||||||
|
errBadComparison = errors.New("incompatible types for comparison")
|
||||||
|
errNoComparison = errors.New("missing argument for comparison")
|
||||||
|
)
|
||||||
|
|
||||||
|
type kind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
invalidKind kind = iota
|
||||||
|
boolKind
|
||||||
|
complexKind
|
||||||
|
intKind
|
||||||
|
floatKind
|
||||||
|
integerKind
|
||||||
|
stringKind
|
||||||
|
uintKind
|
||||||
|
)
|
||||||
|
|
||||||
|
func basicKind(v reflect.Value) (kind, error) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return boolKind, nil
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return intKind, nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return uintKind, nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return floatKind, nil
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return complexKind, nil
|
||||||
|
case reflect.String:
|
||||||
|
return stringKind, nil
|
||||||
|
}
|
||||||
|
return invalidKind, errBadComparisonType
|
||||||
|
}
|
||||||
|
|
||||||
|
// eq evaluates the comparison a == b || a == c || ...
|
||||||
|
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||||
|
v1 := reflect.ValueOf(arg1)
|
||||||
|
k1, err := basicKind(v1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(arg2) == 0 {
|
||||||
|
return false, errNoComparison
|
||||||
|
}
|
||||||
|
for _, arg := range arg2 {
|
||||||
|
v2 := reflect.ValueOf(arg)
|
||||||
|
k2, err := basicKind(v2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
truth := false
|
||||||
|
if k1 != k2 {
|
||||||
|
// Special case: Can compare integer values regardless of type's sign.
|
||||||
|
switch {
|
||||||
|
case k1 == intKind && k2 == uintKind:
|
||||||
|
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||||
|
case k1 == uintKind && k2 == intKind:
|
||||||
|
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||||
|
default:
|
||||||
|
return false, errBadComparison
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch k1 {
|
||||||
|
case boolKind:
|
||||||
|
truth = v1.Bool() == v2.Bool()
|
||||||
|
case complexKind:
|
||||||
|
truth = v1.Complex() == v2.Complex()
|
||||||
|
case floatKind:
|
||||||
|
truth = v1.Float() == v2.Float()
|
||||||
|
case intKind:
|
||||||
|
truth = v1.Int() == v2.Int()
|
||||||
|
case stringKind:
|
||||||
|
truth = v1.String() == v2.String()
|
||||||
|
case uintKind:
|
||||||
|
truth = v1.Uint() == v2.Uint()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if truth {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ne evaluates the comparison a != b.
|
||||||
|
func ne(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// != is the inverse of ==.
|
||||||
|
equal, err := eq(arg1, arg2)
|
||||||
|
return !equal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// lt evaluates the comparison a < b.
|
||||||
|
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
v1 := reflect.ValueOf(arg1)
|
||||||
|
k1, err := basicKind(v1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
v2 := reflect.ValueOf(arg2)
|
||||||
|
k2, err := basicKind(v2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
truth := false
|
||||||
|
if k1 != k2 {
|
||||||
|
// Special case: Can compare integer values regardless of type's sign.
|
||||||
|
switch {
|
||||||
|
case k1 == intKind && k2 == uintKind:
|
||||||
|
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||||
|
case k1 == uintKind && k2 == intKind:
|
||||||
|
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||||
|
default:
|
||||||
|
return false, errBadComparison
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch k1 {
|
||||||
|
case boolKind, complexKind:
|
||||||
|
return false, errBadComparisonType
|
||||||
|
case floatKind:
|
||||||
|
truth = v1.Float() < v2.Float()
|
||||||
|
case intKind:
|
||||||
|
truth = v1.Int() < v2.Int()
|
||||||
|
case stringKind:
|
||||||
|
truth = v1.String() < v2.String()
|
||||||
|
case uintKind:
|
||||||
|
truth = v1.Uint() < v2.Uint()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return truth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// le evaluates the comparison <= b.
|
||||||
|
func le(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// <= is < or ==.
|
||||||
|
lessThan, err := lt(arg1, arg2)
|
||||||
|
if lessThan || err != nil {
|
||||||
|
return lessThan, err
|
||||||
|
}
|
||||||
|
return eq(arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gt evaluates the comparison a > b.
|
||||||
|
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// > is the inverse of <=.
|
||||||
|
lessOrEqual, err := le(arg1, arg2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !lessOrEqual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ge evaluates the comparison a >= b.
|
||||||
|
func ge(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// >= is the inverse of <.
|
||||||
|
lessThan, err := lt(arg1, arg2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !lessThan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlQuot = []byte(""") // shorter than """
|
||||||
|
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||||
|
htmlAmp = []byte("&")
|
||||||
|
htmlLt = []byte("<")
|
||||||
|
htmlGt = []byte(">")
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||||
|
func HTMLEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i, c := range b {
|
||||||
|
var html []byte
|
||||||
|
switch c {
|
||||||
|
case '"':
|
||||||
|
html = htmlQuot
|
||||||
|
case '\'':
|
||||||
|
html = htmlApos
|
||||||
|
case '&':
|
||||||
|
html = htmlAmp
|
||||||
|
case '<':
|
||||||
|
html = htmlLt
|
||||||
|
case '>':
|
||||||
|
html = htmlGt
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
w.Write(html)
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||||
|
func HTMLEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexAny(s, `'"&<>`) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
HTMLEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func HTMLEscaper(args ...interface{}) string {
|
||||||
|
return HTMLEscapeString(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// JavaScript escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsLowUni = []byte(`\u00`)
|
||||||
|
hex = []byte("0123456789ABCDEF")
|
||||||
|
|
||||||
|
jsBackslash = []byte(`\\`)
|
||||||
|
jsApos = []byte(`\'`)
|
||||||
|
jsQuot = []byte(`\"`)
|
||||||
|
jsLt = []byte(`\x3C`)
|
||||||
|
jsGt = []byte(`\x3E`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||||
|
func JSEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
c := b[i]
|
||||||
|
|
||||||
|
if !jsIsSpecial(rune(c)) {
|
||||||
|
// fast path: nothing to do
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
// Quotes, slashes and angle brackets get quoted.
|
||||||
|
// Control characters get written as \u00XX.
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
w.Write(jsBackslash)
|
||||||
|
case '\'':
|
||||||
|
w.Write(jsApos)
|
||||||
|
case '"':
|
||||||
|
w.Write(jsQuot)
|
||||||
|
case '<':
|
||||||
|
w.Write(jsLt)
|
||||||
|
case '>':
|
||||||
|
w.Write(jsGt)
|
||||||
|
default:
|
||||||
|
w.Write(jsLowUni)
|
||||||
|
t, b := c>>4, c&0x0f
|
||||||
|
w.Write(hex[t : t+1])
|
||||||
|
w.Write(hex[b : b+1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unicode rune.
|
||||||
|
r, size := utf8.DecodeRune(b[i:])
|
||||||
|
if unicode.IsPrint(r) {
|
||||||
|
w.Write(b[i : i+size])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "\\u%04X", r)
|
||||||
|
}
|
||||||
|
i += size - 1
|
||||||
|
}
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||||
|
func JSEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
JSEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsIsSpecial(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '\\', '\'', '"', '<', '>':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return r < ' ' || utf8.RuneSelf <= r
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func JSEscaper(args ...interface{}) string {
|
||||||
|
return JSEscapeString(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||||
|
// its arguments in a form suitable for embedding in a URL query.
|
||||||
|
func URLQueryEscaper(args ...interface{}) string {
|
||||||
|
return url.QueryEscape(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||||
|
// fmt.Sprint(args...)
|
||||||
|
// except that each argument is indirected (if a pointer), as required,
|
||||||
|
// using the same rules as the default string evaluation during template
|
||||||
|
// execution.
|
||||||
|
func evalArgs(args []interface{}) string {
|
||||||
|
ok := false
|
||||||
|
var s string
|
||||||
|
// Fast path for simple common case.
|
||||||
|
if len(args) == 1 {
|
||||||
|
s, ok = args[0].(string)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
for i, arg := range args {
|
||||||
|
a, ok := printableValue(reflect.ValueOf(arg))
|
||||||
|
if ok {
|
||||||
|
args[i] = a
|
||||||
|
} // else left fmt do its thing
|
||||||
|
}
|
||||||
|
s = fmt.Sprint(args...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Helper functions to make constructing templates easier.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Functions and methods to parse templates.
|
||||||
|
|
||||||
|
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||||
|
// and panics if the error is non-nil. It is intended for use in variable
|
||||||
|
// initializations such as
|
||||||
|
// var t = template.Must(template.New("name").Parse("text"))
|
||||||
|
func Must(t *Template, err error) *Template {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles creates a new Template and parses the template definitions from
|
||||||
|
// the named files. The returned template's name will have the (base) name and
|
||||||
|
// (parsed) contents of the first file. There must be at least one file.
|
||||||
|
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||||
|
func ParseFiles(filenames ...string) (*Template, error) {
|
||||||
|
return parseFiles(nil, filenames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles parses the named files and associates the resulting templates with
|
||||||
|
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||||
|
// otherwise it is t. There must be at least one file.
|
||||||
|
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||||
|
return parseFiles(t, filenames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFiles is the helper for the method and function. If the argument
|
||||||
|
// template is nil, it is created from the first file.
|
||||||
|
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
// Not really a problem, but be consistent.
|
||||||
|
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||||
|
}
|
||||||
|
for _, filename := range filenames {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
name := filepath.Base(filename)
|
||||||
|
// First template becomes return value if not already defined,
|
||||||
|
// and we use that one for subsequent New calls to associate
|
||||||
|
// all the templates together. Also, if this file has the same name
|
||||||
|
// as t, this file becomes the contents of t, so
|
||||||
|
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||||
|
// works. Otherwise we create a new template associated with t.
|
||||||
|
var tmpl *Template
|
||||||
|
if t == nil {
|
||||||
|
t = New(name)
|
||||||
|
}
|
||||||
|
if name == t.Name() {
|
||||||
|
tmpl = t
|
||||||
|
} else {
|
||||||
|
tmpl = t.New(name)
|
||||||
|
}
|
||||||
|
_, err = tmpl.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGlob creates a new Template and parses the template definitions from the
|
||||||
|
// files identified by the pattern, which must match at least one file. The
|
||||||
|
// returned template will have the (base) name and (parsed) contents of the
|
||||||
|
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||||
|
// ParseFiles with the list of files matched by the pattern.
|
||||||
|
func ParseGlob(pattern string) (*Template, error) {
|
||||||
|
return parseGlob(nil, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGlob parses the template definitions in the files identified by the
|
||||||
|
// pattern and associates the resulting templates with t. The pattern is
|
||||||
|
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||||
|
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||||
|
// pattern.
|
||||||
|
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||||
|
return parseGlob(t, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGlob is the implementation of the function and method ParseGlob.
|
||||||
|
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||||
|
filenames, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||||
|
}
|
||||||
|
return parseFiles(t, filenames...)
|
||||||
|
}
|
||||||
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// item represents a token or text string returned from the scanner.
|
||||||
|
type item struct {
|
||||||
|
typ itemType // The type of this item.
|
||||||
|
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||||
|
val string // The value of this item.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i item) String() string {
|
||||||
|
switch {
|
||||||
|
case i.typ == itemEOF:
|
||||||
|
return "EOF"
|
||||||
|
case i.typ == itemError:
|
||||||
|
return i.val
|
||||||
|
case i.typ > itemKeyword:
|
||||||
|
return fmt.Sprintf("<%s>", i.val)
|
||||||
|
case len(i.val) > 10:
|
||||||
|
return fmt.Sprintf("%.10q...", i.val)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", i.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemType identifies the type of lex items.
|
||||||
|
type itemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
itemError itemType = iota // error occurred; value is text of error
|
||||||
|
itemBool // boolean constant
|
||||||
|
itemChar // printable ASCII character; grab bag for comma etc.
|
||||||
|
itemCharConstant // character constant
|
||||||
|
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||||
|
itemColonEquals // colon-equals (':=') introducing a declaration
|
||||||
|
itemEOF
|
||||||
|
itemField // alphanumeric identifier starting with '.'
|
||||||
|
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||||
|
itemLeftDelim // left action delimiter
|
||||||
|
itemLeftParen // '(' inside action
|
||||||
|
itemNumber // simple number, including imaginary
|
||||||
|
itemPipe // pipe symbol
|
||||||
|
itemRawString // raw quoted string (includes quotes)
|
||||||
|
itemRightDelim // right action delimiter
|
||||||
|
itemElideNewline // elide newline after right delim
|
||||||
|
itemRightParen // ')' inside action
|
||||||
|
itemSpace // run of spaces separating arguments
|
||||||
|
itemString // quoted string (includes quotes)
|
||||||
|
itemText // plain text
|
||||||
|
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||||
|
// Keywords appear after all the rest.
|
||||||
|
itemKeyword // used only to delimit the keywords
|
||||||
|
itemDot // the cursor, spelled '.'
|
||||||
|
itemDefine // define keyword
|
||||||
|
itemElse // else keyword
|
||||||
|
itemEnd // end keyword
|
||||||
|
itemIf // if keyword
|
||||||
|
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||||
|
itemRange // range keyword
|
||||||
|
itemTemplate // template keyword
|
||||||
|
itemWith // with keyword
|
||||||
|
)
|
||||||
|
|
||||||
|
var key = map[string]itemType{
|
||||||
|
".": itemDot,
|
||||||
|
"define": itemDefine,
|
||||||
|
"else": itemElse,
|
||||||
|
"end": itemEnd,
|
||||||
|
"if": itemIf,
|
||||||
|
"range": itemRange,
|
||||||
|
"nil": itemNil,
|
||||||
|
"template": itemTemplate,
|
||||||
|
"with": itemWith,
|
||||||
|
}
|
||||||
|
|
||||||
|
const eof = -1
|
||||||
|
|
||||||
|
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||||
|
type stateFn func(*lexer) stateFn
|
||||||
|
|
||||||
|
// lexer holds the state of the scanner.
|
||||||
|
type lexer struct {
|
||||||
|
name string // the name of the input; used only for error reports
|
||||||
|
input string // the string being scanned
|
||||||
|
leftDelim string // start of action
|
||||||
|
rightDelim string // end of action
|
||||||
|
state stateFn // the next lexing function to enter
|
||||||
|
pos Pos // current position in the input
|
||||||
|
start Pos // start position of this item
|
||||||
|
width Pos // width of last rune read from input
|
||||||
|
lastPos Pos // position of most recent item returned by nextItem
|
||||||
|
items chan item // channel of scanned items
|
||||||
|
parenDepth int // nesting depth of ( ) exprs
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next rune in the input.
|
||||||
|
func (l *lexer) next() rune {
|
||||||
|
if int(l.pos) >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.width = Pos(w)
|
||||||
|
l.pos += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next rune in the input.
|
||||||
|
func (l *lexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one rune. Can only be called once per call of next.
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emit(t itemType) {
|
||||||
|
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore skips over the pending input before this point.
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept consumes the next rune if it's from the valid set.
|
||||||
|
func (l *lexer) accept(valid string) bool {
|
||||||
|
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptRun consumes a run of runes from the valid set.
|
||||||
|
func (l *lexer) acceptRun(valid string) {
|
||||||
|
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lineNumber reports which line we're on, based on the position of
|
||||||
|
// the previous item returned by nextItem. Doing it this way
|
||||||
|
// means we don't have to worry about peek double counting.
|
||||||
|
func (l *lexer) lineNumber() int {
|
||||||
|
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf returns an error token and terminates the scan by passing
|
||||||
|
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||||
|
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||||
|
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextItem returns the next item from the input.
|
||||||
|
func (l *lexer) nextItem() item {
|
||||||
|
item := <-l.items
|
||||||
|
l.lastPos = item.pos
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// lex creates a new scanner for the input string.
|
||||||
|
func lex(name, input, left, right string) *lexer {
|
||||||
|
if left == "" {
|
||||||
|
left = leftDelim
|
||||||
|
}
|
||||||
|
if right == "" {
|
||||||
|
right = rightDelim
|
||||||
|
}
|
||||||
|
l := &lexer{
|
||||||
|
name: name,
|
||||||
|
input: input,
|
||||||
|
leftDelim: left,
|
||||||
|
rightDelim: right,
|
||||||
|
items: make(chan item),
|
||||||
|
}
|
||||||
|
go l.run()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// run runs the state machine for the lexer.
|
||||||
|
func (l *lexer) run() {
|
||||||
|
for l.state = lexText; l.state != nil; {
|
||||||
|
l.state = l.state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// state functions
|
||||||
|
|
||||||
|
const (
|
||||||
|
leftDelim = "{{"
|
||||||
|
rightDelim = "}}"
|
||||||
|
leftComment = "/*"
|
||||||
|
rightComment = "*/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lexText scans until an opening action delimiter, "{{".
|
||||||
|
func lexText(l *lexer) stateFn {
|
||||||
|
for {
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(itemText)
|
||||||
|
}
|
||||||
|
return lexLeftDelim
|
||||||
|
}
|
||||||
|
if l.next() == eof {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Correctly reached EOF.
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(itemText)
|
||||||
|
}
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexLeftDelim scans the left delimiter, which is known to be present.
|
||||||
|
func lexLeftDelim(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(l.leftDelim))
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
||||||
|
return lexComment
|
||||||
|
}
|
||||||
|
l.emit(itemLeftDelim)
|
||||||
|
l.parenDepth = 0
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexComment scans a comment. The left comment marker is known to be present.
|
||||||
|
func lexComment(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(leftComment))
|
||||||
|
i := strings.Index(l.input[l.pos:], rightComment)
|
||||||
|
if i < 0 {
|
||||||
|
return l.errorf("unclosed comment")
|
||||||
|
}
|
||||||
|
l.pos += Pos(i + len(rightComment))
|
||||||
|
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||||
|
return l.errorf("comment ends before closing delimiter")
|
||||||
|
|
||||||
|
}
|
||||||
|
l.pos += Pos(len(l.rightDelim))
|
||||||
|
l.ignore()
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRightDelim scans the right delimiter, which is known to be present.
|
||||||
|
func lexRightDelim(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(l.rightDelim))
|
||||||
|
l.emit(itemRightDelim)
|
||||||
|
if l.peek() == '\\' {
|
||||||
|
l.pos++
|
||||||
|
l.emit(itemElideNewline)
|
||||||
|
}
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexInsideAction scans the elements inside action delimiters.
|
||||||
|
func lexInsideAction(l *lexer) stateFn {
|
||||||
|
// Either number, quoted string, or identifier.
|
||||||
|
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||||
|
// Pipe symbols separate and are emitted.
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||||
|
if l.parenDepth == 0 {
|
||||||
|
return lexRightDelim
|
||||||
|
}
|
||||||
|
return l.errorf("unclosed left paren")
|
||||||
|
}
|
||||||
|
switch r := l.next(); {
|
||||||
|
case r == eof || isEndOfLine(r):
|
||||||
|
return l.errorf("unclosed action")
|
||||||
|
case isSpace(r):
|
||||||
|
return lexSpace
|
||||||
|
case r == ':':
|
||||||
|
if l.next() != '=' {
|
||||||
|
return l.errorf("expected :=")
|
||||||
|
}
|
||||||
|
l.emit(itemColonEquals)
|
||||||
|
case r == '|':
|
||||||
|
l.emit(itemPipe)
|
||||||
|
case r == '"':
|
||||||
|
return lexQuote
|
||||||
|
case r == '`':
|
||||||
|
return lexRawQuote
|
||||||
|
case r == '$':
|
||||||
|
return lexVariable
|
||||||
|
case r == '\'':
|
||||||
|
return lexChar
|
||||||
|
case r == '.':
|
||||||
|
// special look-ahead for ".field" so we don't break l.backup().
|
||||||
|
if l.pos < Pos(len(l.input)) {
|
||||||
|
r := l.input[l.pos]
|
||||||
|
if r < '0' || '9' < r {
|
||||||
|
return lexField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallthrough // '.' can start a number.
|
||||||
|
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||||
|
l.backup()
|
||||||
|
return lexNumber
|
||||||
|
case isAlphaNumeric(r):
|
||||||
|
l.backup()
|
||||||
|
return lexIdentifier
|
||||||
|
case r == '(':
|
||||||
|
l.emit(itemLeftParen)
|
||||||
|
l.parenDepth++
|
||||||
|
return lexInsideAction
|
||||||
|
case r == ')':
|
||||||
|
l.emit(itemRightParen)
|
||||||
|
l.parenDepth--
|
||||||
|
if l.parenDepth < 0 {
|
||||||
|
return l.errorf("unexpected right paren %#U", r)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||||
|
l.emit(itemChar)
|
||||||
|
return lexInsideAction
|
||||||
|
default:
|
||||||
|
return l.errorf("unrecognized character in action: %#U", r)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexSpace scans a run of space characters.
|
||||||
|
// One space has already been seen.
|
||||||
|
func lexSpace(l *lexer) stateFn {
|
||||||
|
for isSpace(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
}
|
||||||
|
l.emit(itemSpace)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexIdentifier scans an alphanumeric.
|
||||||
|
func lexIdentifier(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch r := l.next(); {
|
||||||
|
case isAlphaNumeric(r):
|
||||||
|
// absorb.
|
||||||
|
default:
|
||||||
|
l.backup()
|
||||||
|
word := l.input[l.start:l.pos]
|
||||||
|
if !l.atTerminator() {
|
||||||
|
return l.errorf("bad character %#U", r)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case key[word] > itemKeyword:
|
||||||
|
l.emit(key[word])
|
||||||
|
case word[0] == '.':
|
||||||
|
l.emit(itemField)
|
||||||
|
case word == "true", word == "false":
|
||||||
|
l.emit(itemBool)
|
||||||
|
default:
|
||||||
|
l.emit(itemIdentifier)
|
||||||
|
}
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexField scans a field: .Alphanumeric.
|
||||||
|
// The . has been scanned.
|
||||||
|
func lexField(l *lexer) stateFn {
|
||||||
|
return lexFieldOrVariable(l, itemField)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexVariable scans a Variable: $Alphanumeric.
|
||||||
|
// The $ has been scanned.
|
||||||
|
func lexVariable(l *lexer) stateFn {
|
||||||
|
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||||
|
l.emit(itemVariable)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
return lexFieldOrVariable(l, itemVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||||
|
// The . or $ has been scanned.
|
||||||
|
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||||
|
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||||
|
if typ == itemVariable {
|
||||||
|
l.emit(itemVariable)
|
||||||
|
} else {
|
||||||
|
l.emit(itemDot)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
var r rune
|
||||||
|
for {
|
||||||
|
r = l.next()
|
||||||
|
if !isAlphaNumeric(r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !l.atTerminator() {
|
||||||
|
return l.errorf("bad character %#U", r)
|
||||||
|
}
|
||||||
|
l.emit(typ)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// atTerminator reports whether the input is at valid termination character to
|
||||||
|
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||||
|
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||||
|
// day to implement arithmetic.
|
||||||
|
func (l *lexer) atTerminator() bool {
|
||||||
|
r := l.peek()
|
||||||
|
if isSpace(r) || isEndOfLine(r) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case eof, '.', ',', '|', ':', ')', '(':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||||
|
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||||
|
// bad choice of delimiter.
|
||||||
|
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexChar scans a character constant. The initial quote is already
|
||||||
|
// scanned. Syntax checking is done by the parser.
|
||||||
|
func lexChar(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != eof && r != '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated character constant")
|
||||||
|
case '\'':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemCharConstant)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||||
|
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||||
|
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||||
|
// strconv) will notice.
|
||||||
|
func lexNumber(l *lexer) stateFn {
|
||||||
|
if !l.scanNumber() {
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||||
|
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||||
|
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(itemComplex)
|
||||||
|
} else {
|
||||||
|
l.emit(itemNumber)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) scanNumber() bool {
|
||||||
|
// Optional leading sign.
|
||||||
|
l.accept("+-")
|
||||||
|
// Is it hex?
|
||||||
|
digits := "0123456789"
|
||||||
|
if l.accept("0") && l.accept("xX") {
|
||||||
|
digits = "0123456789abcdefABCDEF"
|
||||||
|
}
|
||||||
|
l.acceptRun(digits)
|
||||||
|
if l.accept(".") {
|
||||||
|
l.acceptRun(digits)
|
||||||
|
}
|
||||||
|
if l.accept("eE") {
|
||||||
|
l.accept("+-")
|
||||||
|
l.acceptRun("0123456789")
|
||||||
|
}
|
||||||
|
// Is it imaginary?
|
||||||
|
l.accept("i")
|
||||||
|
// Next thing mustn't be alphanumeric.
|
||||||
|
if isAlphaNumeric(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexQuote scans a quoted string.
|
||||||
|
func lexQuote(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != eof && r != '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated quoted string")
|
||||||
|
case '"':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemString)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRawQuote scans a raw quoted string.
|
||||||
|
func lexRawQuote(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated raw quoted string")
|
||||||
|
case '`':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemRawString)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpace reports whether r is a space character.
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
return r == ' ' || r == '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEndOfLine reports whether r is an end-of-line character.
|
||||||
|
func isEndOfLine(r rune) bool {
|
||||||
|
return r == '\r' || r == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||||
|
func isAlphaNumeric(r rune) bool {
|
||||||
|
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||||
|
}
|
||||||
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
Normal file
@ -0,0 +1,834 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Parse nodes.
|
||||||
|
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||||
|
|
||||||
|
// A Node is an element in the parse tree. The interface is trivial.
|
||||||
|
// The interface contains an unexported method so that only
|
||||||
|
// types local to this package can satisfy it.
|
||||||
|
type Node interface {
|
||||||
|
Type() NodeType
|
||||||
|
String() string
|
||||||
|
// Copy does a deep copy of the Node and all its components.
|
||||||
|
// To avoid type assertions, some XxxNodes also have specialized
|
||||||
|
// CopyXxx methods that return *XxxNode.
|
||||||
|
Copy() Node
|
||||||
|
Position() Pos // byte position of start of node in full original input string
|
||||||
|
// tree returns the containing *Tree.
|
||||||
|
// It is unexported so all implementations of Node are in this package.
|
||||||
|
tree() *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeType identifies the type of a parse tree node.
|
||||||
|
type NodeType int
|
||||||
|
|
||||||
|
// Pos represents a byte position in the original input text from which
|
||||||
|
// this template was parsed.
|
||||||
|
type Pos int
|
||||||
|
|
||||||
|
func (p Pos) Position() Pos {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns itself and provides an easy default implementation
|
||||||
|
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||||
|
func (t NodeType) Type() NodeType {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NodeText NodeType = iota // Plain text.
|
||||||
|
NodeAction // A non-control action such as a field evaluation.
|
||||||
|
NodeBool // A boolean constant.
|
||||||
|
NodeChain // A sequence of field accesses.
|
||||||
|
NodeCommand // An element of a pipeline.
|
||||||
|
NodeDot // The cursor, dot.
|
||||||
|
nodeElse // An else action. Not added to tree.
|
||||||
|
nodeEnd // An end action. Not added to tree.
|
||||||
|
NodeField // A field or method name.
|
||||||
|
NodeIdentifier // An identifier; always a function name.
|
||||||
|
NodeIf // An if action.
|
||||||
|
NodeList // A list of Nodes.
|
||||||
|
NodeNil // An untyped nil constant.
|
||||||
|
NodeNumber // A numerical constant.
|
||||||
|
NodePipe // A pipeline of commands.
|
||||||
|
NodeRange // A range action.
|
||||||
|
NodeString // A string constant.
|
||||||
|
NodeTemplate // A template invocation action.
|
||||||
|
NodeVariable // A $ variable.
|
||||||
|
NodeWith // A with action.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nodes.
|
||||||
|
|
||||||
|
// ListNode holds a sequence of nodes.
|
||||||
|
type ListNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Nodes []Node // The element nodes in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newList(pos Pos) *ListNode {
|
||||||
|
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) append(n Node) {
|
||||||
|
l.Nodes = append(l.Nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) tree() *Tree {
|
||||||
|
return l.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) String() string {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
for _, n := range l.Nodes {
|
||||||
|
fmt.Fprint(b, n)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) CopyList() *ListNode {
|
||||||
|
if l == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
n := l.tr.newList(l.Pos)
|
||||||
|
for _, elem := range l.Nodes {
|
||||||
|
n.append(elem.Copy())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) Copy() Node {
|
||||||
|
return l.CopyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextNode holds plain text.
|
||||||
|
type TextNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Text []byte // The text; may span newlines.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||||
|
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) String() string {
|
||||||
|
return fmt.Sprintf(textFormat, t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) tree() *Tree {
|
||||||
|
return t.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) Copy() Node {
|
||||||
|
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeNode holds a pipeline with optional declaration
|
||||||
|
type PipeNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Decl []*VariableNode // Variable declarations in lexical order.
|
||||||
|
Cmds []*CommandNode // The commands in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
||||||
|
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) append(command *CommandNode) {
|
||||||
|
p.Cmds = append(p.Cmds, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) String() string {
|
||||||
|
s := ""
|
||||||
|
if len(p.Decl) > 0 {
|
||||||
|
for i, v := range p.Decl {
|
||||||
|
if i > 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += v.String()
|
||||||
|
}
|
||||||
|
s += " := "
|
||||||
|
}
|
||||||
|
for i, c := range p.Cmds {
|
||||||
|
if i > 0 {
|
||||||
|
s += " | "
|
||||||
|
}
|
||||||
|
s += c.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) tree() *Tree {
|
||||||
|
return p.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||||
|
if p == nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
var decl []*VariableNode
|
||||||
|
for _, d := range p.Decl {
|
||||||
|
decl = append(decl, d.Copy().(*VariableNode))
|
||||||
|
}
|
||||||
|
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
||||||
|
for _, c := range p.Cmds {
|
||||||
|
n.append(c.Copy().(*CommandNode))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) Copy() Node {
|
||||||
|
return p.CopyPipe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionNode holds an action (something bounded by delimiters).
|
||||||
|
// Control actions have their own nodes; ActionNode represents simple
|
||||||
|
// ones such as field evaluations and parenthesized pipelines.
|
||||||
|
type ActionNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Pipe *PipeNode // The pipeline in the action.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||||
|
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) String() string {
|
||||||
|
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) tree() *Tree {
|
||||||
|
return a.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) Copy() Node {
|
||||||
|
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||||
|
type CommandNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||||
|
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) append(arg Node) {
|
||||||
|
c.Args = append(c.Args, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for i, arg := range c.Args {
|
||||||
|
if i > 0 {
|
||||||
|
s += " "
|
||||||
|
}
|
||||||
|
if arg, ok := arg.(*PipeNode); ok {
|
||||||
|
s += "(" + arg.String() + ")"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s += arg.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) tree() *Tree {
|
||||||
|
return c.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) Copy() Node {
|
||||||
|
if c == nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
n := c.tr.newCommand(c.Pos)
|
||||||
|
for _, c := range c.Args {
|
||||||
|
n.append(c.Copy())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentifierNode holds an identifier.
|
||||||
|
type IdentifierNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident string // The identifier's name.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||||
|
func NewIdentifier(ident string) *IdentifierNode {
|
||||||
|
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||||
|
// Chained for convenience.
|
||||||
|
// TODO: fix one day?
|
||||||
|
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||||
|
i.Pos = pos
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||||
|
// Chained for convenience.
|
||||||
|
// TODO: fix one day?
|
||||||
|
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||||
|
i.tr = t
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) String() string {
|
||||||
|
return i.Ident
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) tree() *Tree {
|
||||||
|
return i.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) Copy() Node {
|
||||||
|
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariableNode holds a list of variable names, possibly with chained field
|
||||||
|
// accesses. The dollar sign is part of the (first) name.
|
||||||
|
type VariableNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident []string // Variable name and fields in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||||
|
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for i, id := range v.Ident {
|
||||||
|
if i > 0 {
|
||||||
|
s += "."
|
||||||
|
}
|
||||||
|
s += id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) tree() *Tree {
|
||||||
|
return v.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) Copy() Node {
|
||||||
|
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DotNode holds the special identifier '.'.
|
||||||
|
type DotNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||||
|
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) Type() NodeType {
|
||||||
|
// Override method on embedded NodeType for API compatibility.
|
||||||
|
// TODO: Not really a problem; could change API without effect but
|
||||||
|
// api tool complains.
|
||||||
|
return NodeDot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) String() string {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) tree() *Tree {
|
||||||
|
return d.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) Copy() Node {
|
||||||
|
return d.tr.newDot(d.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||||
|
type NilNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||||
|
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) Type() NodeType {
|
||||||
|
// Override method on embedded NodeType for API compatibility.
|
||||||
|
// TODO: Not really a problem; could change API without effect but
|
||||||
|
// api tool complains.
|
||||||
|
return NodeNil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) String() string {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) tree() *Tree {
|
||||||
|
return n.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) Copy() Node {
|
||||||
|
return n.tr.newNil(n.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldNode holds a field (identifier starting with '.').
|
||||||
|
// The names may be chained ('.x.y').
|
||||||
|
// The period is dropped from each ident.
|
||||||
|
type FieldNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident []string // The identifiers in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||||
|
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for _, id := range f.Ident {
|
||||||
|
s += "." + id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) tree() *Tree {
|
||||||
|
return f.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) Copy() Node {
|
||||||
|
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||||
|
// The names may be chained ('.x.y').
|
||||||
|
// The periods are dropped from each ident.
|
||||||
|
type ChainNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Node Node
|
||||||
|
Field []string // The identifiers in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||||
|
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||||
|
func (c *ChainNode) Add(field string) {
|
||||||
|
if len(field) == 0 || field[0] != '.' {
|
||||||
|
panic("no dot in field")
|
||||||
|
}
|
||||||
|
field = field[1:] // Remove leading dot.
|
||||||
|
if field == "" {
|
||||||
|
panic("empty field")
|
||||||
|
}
|
||||||
|
c.Field = append(c.Field, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) String() string {
|
||||||
|
s := c.Node.String()
|
||||||
|
if _, ok := c.Node.(*PipeNode); ok {
|
||||||
|
s = "(" + s + ")"
|
||||||
|
}
|
||||||
|
for _, field := range c.Field {
|
||||||
|
s += "." + field
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) tree() *Tree {
|
||||||
|
return c.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) Copy() Node {
|
||||||
|
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolNode holds a boolean constant.
|
||||||
|
type BoolNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
True bool // The value of the boolean constant.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||||
|
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) String() string {
|
||||||
|
if b.True {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) tree() *Tree {
|
||||||
|
return b.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) Copy() Node {
|
||||||
|
return b.tr.newBool(b.Pos, b.True)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||||
|
// The value is parsed and stored under all the types that can represent the value.
|
||||||
|
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||||
|
type NumberNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
IsInt bool // Number has an integral value.
|
||||||
|
IsUint bool // Number has an unsigned integral value.
|
||||||
|
IsFloat bool // Number has a floating-point value.
|
||||||
|
IsComplex bool // Number is complex.
|
||||||
|
Int64 int64 // The signed integer value.
|
||||||
|
Uint64 uint64 // The unsigned integer value.
|
||||||
|
Float64 float64 // The floating-point value.
|
||||||
|
Complex128 complex128 // The complex value.
|
||||||
|
Text string // The original textual representation from the input.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||||
|
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||||
|
switch typ {
|
||||||
|
case itemCharConstant:
|
||||||
|
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tail != "'" {
|
||||||
|
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||||
|
}
|
||||||
|
n.Int64 = int64(rune)
|
||||||
|
n.IsInt = true
|
||||||
|
n.Uint64 = uint64(rune)
|
||||||
|
n.IsUint = true
|
||||||
|
n.Float64 = float64(rune) // odd but those are the rules.
|
||||||
|
n.IsFloat = true
|
||||||
|
return n, nil
|
||||||
|
case itemComplex:
|
||||||
|
// fmt.Sscan can parse the pair, so let it do the work.
|
||||||
|
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.IsComplex = true
|
||||||
|
n.simplifyComplex()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
// Imaginary constants can only be complex unless they are zero.
|
||||||
|
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||||
|
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsComplex = true
|
||||||
|
n.Complex128 = complex(0, f)
|
||||||
|
n.simplifyComplex()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do integer test first so we get 0x123 etc.
|
||||||
|
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||||
|
if err == nil {
|
||||||
|
n.IsUint = true
|
||||||
|
n.Uint64 = u
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseInt(text, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsInt = true
|
||||||
|
n.Int64 = i
|
||||||
|
if i == 0 {
|
||||||
|
n.IsUint = true // in case of -0.
|
||||||
|
n.Uint64 = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If an integer extraction succeeded, promote the float.
|
||||||
|
if n.IsInt {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = float64(n.Int64)
|
||||||
|
} else if n.IsUint {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = float64(n.Uint64)
|
||||||
|
} else {
|
||||||
|
f, err := strconv.ParseFloat(text, 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = f
|
||||||
|
// If a floating-point extraction succeeded, extract the int if needed.
|
||||||
|
if !n.IsInt && float64(int64(f)) == f {
|
||||||
|
n.IsInt = true
|
||||||
|
n.Int64 = int64(f)
|
||||||
|
}
|
||||||
|
if !n.IsUint && float64(uint64(f)) == f {
|
||||||
|
n.IsUint = true
|
||||||
|
n.Uint64 = uint64(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||||
|
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||||
|
// These all require that the imaginary part be zero.
|
||||||
|
func (n *NumberNode) simplifyComplex() {
|
||||||
|
n.IsFloat = imag(n.Complex128) == 0
|
||||||
|
if n.IsFloat {
|
||||||
|
n.Float64 = real(n.Complex128)
|
||||||
|
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||||
|
if n.IsInt {
|
||||||
|
n.Int64 = int64(n.Float64)
|
||||||
|
}
|
||||||
|
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||||
|
if n.IsUint {
|
||||||
|
n.Uint64 = uint64(n.Float64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) String() string {
|
||||||
|
return n.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) tree() *Tree {
|
||||||
|
return n.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) Copy() Node {
|
||||||
|
nn := new(NumberNode)
|
||||||
|
*nn = *n // Easy, fast, correct.
|
||||||
|
return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringNode holds a string constant. The value has been "unquoted".
|
||||||
|
type StringNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Quoted string // The original text of the string, with quotes.
|
||||||
|
Text string // The string, after quote processing.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||||
|
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) String() string {
|
||||||
|
return s.Quoted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) tree() *Tree {
|
||||||
|
return s.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) Copy() Node {
|
||||||
|
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endNode represents an {{end}} action.
|
||||||
|
// It does not appear in the final parse tree.
|
||||||
|
type endNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||||
|
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) String() string {
|
||||||
|
return "{{end}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) tree() *Tree {
|
||||||
|
return e.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) Copy() Node {
|
||||||
|
return e.tr.newEnd(e.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||||
|
type elseNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||||
|
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) Type() NodeType {
|
||||||
|
return nodeElse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) String() string {
|
||||||
|
return "{{else}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) tree() *Tree {
|
||||||
|
return e.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) Copy() Node {
|
||||||
|
return e.tr.newElse(e.Pos, e.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BranchNode is the common representation of if, range, and with.
|
||||||
|
type BranchNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Pipe *PipeNode // The pipeline to be evaluated.
|
||||||
|
List *ListNode // What to execute if the value is non-empty.
|
||||||
|
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) String() string {
|
||||||
|
name := ""
|
||||||
|
switch b.NodeType {
|
||||||
|
case NodeIf:
|
||||||
|
name = "if"
|
||||||
|
case NodeRange:
|
||||||
|
name = "range"
|
||||||
|
case NodeWith:
|
||||||
|
name = "with"
|
||||||
|
default:
|
||||||
|
panic("unknown branch type")
|
||||||
|
}
|
||||||
|
if b.ElseList != nil {
|
||||||
|
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) tree() *Tree {
|
||||||
|
return b.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) Copy() Node {
|
||||||
|
switch b.NodeType {
|
||||||
|
case NodeIf:
|
||||||
|
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
case NodeRange:
|
||||||
|
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
case NodeWith:
|
||||||
|
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
default:
|
||||||
|
panic("unknown branch type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfNode represents an {{if}} action and its commands.
|
||||||
|
type IfNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||||
|
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IfNode) Copy() Node {
|
||||||
|
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeNode represents a {{range}} action and its commands.
|
||||||
|
type RangeNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||||
|
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RangeNode) Copy() Node {
|
||||||
|
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNode represents a {{with}} action and its commands.
|
||||||
|
type WithNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||||
|
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WithNode) Copy() Node {
|
||||||
|
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateNode represents a {{template}} action.
|
||||||
|
type TemplateNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Name string // The name of the template (unquoted).
|
||||||
|
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||||
|
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) String() string {
|
||||||
|
if t.Pipe == nil {
|
||||||
|
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) tree() *Tree {
|
||||||
|
return t.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) Copy() Node {
|
||||||
|
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||||
|
}
|
||||||
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
Normal file
@ -0,0 +1,700 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package parse builds parse trees for templates as defined by text/template
|
||||||
|
// and html/template. Clients should use those packages to construct templates
|
||||||
|
// rather than this one, which provides shared internal data structures not
|
||||||
|
// intended for general use.
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tree is the representation of a single parsed template.
|
||||||
|
type Tree struct {
|
||||||
|
Name string // name of the template represented by the tree.
|
||||||
|
ParseName string // name of the top-level template during parsing, for error messages.
|
||||||
|
Root *ListNode // top-level root of the tree.
|
||||||
|
text string // text parsed to create the template (or its parent)
|
||||||
|
// Parsing only; cleared after parse.
|
||||||
|
funcs []map[string]interface{}
|
||||||
|
lex *lexer
|
||||||
|
token [3]item // three-token lookahead for parser.
|
||||||
|
peekCount int
|
||||||
|
vars []string // variables defined at the moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||||
|
func (t *Tree) Copy() *Tree {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Tree{
|
||||||
|
Name: t.Name,
|
||||||
|
ParseName: t.ParseName,
|
||||||
|
Root: t.Root.CopyList(),
|
||||||
|
text: t.text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||||
|
// templates described in the argument string. The top-level template will be
|
||||||
|
// given the specified name. If an error is encountered, parsing stops and an
|
||||||
|
// empty map is returned with the error.
|
||||||
|
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
||||||
|
treeSet = make(map[string]*Tree)
|
||||||
|
t := New(name)
|
||||||
|
t.text = text
|
||||||
|
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next token.
|
||||||
|
func (t *Tree) next() item {
|
||||||
|
if t.peekCount > 0 {
|
||||||
|
t.peekCount--
|
||||||
|
} else {
|
||||||
|
t.token[0] = t.lex.nextItem()
|
||||||
|
}
|
||||||
|
return t.token[t.peekCount]
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup backs the input stream up one token.
|
||||||
|
func (t *Tree) backup() {
|
||||||
|
t.peekCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup2 backs the input stream up two tokens.
|
||||||
|
// The zeroth token is already there.
|
||||||
|
func (t *Tree) backup2(t1 item) {
|
||||||
|
t.token[1] = t1
|
||||||
|
t.peekCount = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup3 backs the input stream up three tokens
|
||||||
|
// The zeroth token is already there.
|
||||||
|
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||||
|
t.token[1] = t1
|
||||||
|
t.token[2] = t2
|
||||||
|
t.peekCount = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next token.
|
||||||
|
func (t *Tree) peek() item {
|
||||||
|
if t.peekCount > 0 {
|
||||||
|
return t.token[t.peekCount-1]
|
||||||
|
}
|
||||||
|
t.peekCount = 1
|
||||||
|
t.token[0] = t.lex.nextItem()
|
||||||
|
return t.token[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextNonSpace returns the next non-space token.
|
||||||
|
func (t *Tree) nextNonSpace() (token item) {
|
||||||
|
for {
|
||||||
|
token = t.next()
|
||||||
|
if token.typ != itemSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekNonSpace returns but does not consume the next non-space token.
|
||||||
|
func (t *Tree) peekNonSpace() (token item) {
|
||||||
|
for {
|
||||||
|
token = t.next()
|
||||||
|
if token.typ != itemSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing.
|
||||||
|
|
||||||
|
// New allocates a new parse tree with the given name.
|
||||||
|
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||||
|
return &Tree{
|
||||||
|
Name: name,
|
||||||
|
funcs: funcs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||||
|
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||||
|
// which can occur in old code.
|
||||||
|
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||||
|
pos := int(n.Position())
|
||||||
|
tree := n.tree()
|
||||||
|
if tree == nil {
|
||||||
|
tree = t
|
||||||
|
}
|
||||||
|
text := tree.text[:pos]
|
||||||
|
byteNum := strings.LastIndex(text, "\n")
|
||||||
|
if byteNum == -1 {
|
||||||
|
byteNum = pos // On first line.
|
||||||
|
} else {
|
||||||
|
byteNum++ // After the newline.
|
||||||
|
byteNum = pos - byteNum
|
||||||
|
}
|
||||||
|
lineNum := 1 + strings.Count(text, "\n")
|
||||||
|
context = n.String()
|
||||||
|
if len(context) > 20 {
|
||||||
|
context = fmt.Sprintf("%.20s...", context)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf formats the error and terminates processing.
|
||||||
|
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||||
|
t.Root = nil
|
||||||
|
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// error terminates processing.
|
||||||
|
func (t *Tree) error(err error) {
|
||||||
|
t.errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect consumes the next token and guarantees it has the required type.
|
||||||
|
func (t *Tree) expect(expected itemType, context string) item {
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
if token.typ != expected {
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||||
|
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
if token.typ != expected1 && token.typ != expected2 {
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// unexpected complains about the token and terminates processing.
|
||||||
|
func (t *Tree) unexpected(token item, context string) {
|
||||||
|
t.errorf("unexpected %s in %s", token, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||||
|
func (t *Tree) recover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
if _, ok := e.(runtime.Error); ok {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
t.stopParse()
|
||||||
|
}
|
||||||
|
*errp = e.(error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// startParse initializes the parser, using the lexer.
|
||||||
|
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
||||||
|
t.Root = nil
|
||||||
|
t.lex = lex
|
||||||
|
t.vars = []string{"$"}
|
||||||
|
t.funcs = funcs
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopParse terminates parsing.
|
||||||
|
func (t *Tree) stopParse() {
|
||||||
|
t.lex = nil
|
||||||
|
t.vars = nil
|
||||||
|
t.funcs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the template definition string to construct a representation of
|
||||||
|
// the template for execution. If either action delimiter string is empty, the
|
||||||
|
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||||
|
// the treeSet map.
|
||||||
|
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||||
|
defer t.recover(&err)
|
||||||
|
t.ParseName = t.Name
|
||||||
|
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
||||||
|
t.text = text
|
||||||
|
t.parse(treeSet)
|
||||||
|
t.add(treeSet)
|
||||||
|
t.stopParse()
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds tree to the treeSet.
|
||||||
|
func (t *Tree) add(treeSet map[string]*Tree) {
|
||||||
|
tree := treeSet[t.Name]
|
||||||
|
if tree == nil || IsEmptyTree(tree.Root) {
|
||||||
|
treeSet[t.Name] = t
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !IsEmptyTree(t.Root) {
|
||||||
|
t.errorf("template: multiple definition of template %q", t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||||
|
func IsEmptyTree(n Node) bool {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case nil:
|
||||||
|
return true
|
||||||
|
case *ActionNode:
|
||||||
|
case *IfNode:
|
||||||
|
case *ListNode:
|
||||||
|
for _, node := range n.Nodes {
|
||||||
|
if !IsEmptyTree(node) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case *RangeNode:
|
||||||
|
case *TemplateNode:
|
||||||
|
case *TextNode:
|
||||||
|
return len(bytes.TrimSpace(n.Text)) == 0
|
||||||
|
case *WithNode:
|
||||||
|
default:
|
||||||
|
panic("unknown node: " + n.String())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse is the top-level parser for a template, essentially the same
|
||||||
|
// as itemList except it also parses {{define}} actions.
|
||||||
|
// It runs to EOF.
|
||||||
|
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||||
|
t.Root = t.newList(t.peek().pos)
|
||||||
|
for t.peek().typ != itemEOF {
|
||||||
|
if t.peek().typ == itemLeftDelim {
|
||||||
|
delim := t.next()
|
||||||
|
if t.nextNonSpace().typ == itemDefine {
|
||||||
|
newT := New("definition") // name will be updated once we know it.
|
||||||
|
newT.text = t.text
|
||||||
|
newT.ParseName = t.ParseName
|
||||||
|
newT.startParse(t.funcs, t.lex)
|
||||||
|
newT.parseDefinition(treeSet)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.backup2(delim)
|
||||||
|
}
|
||||||
|
n := t.textOrAction()
|
||||||
|
if n.Type() == nodeEnd {
|
||||||
|
t.errorf("unexpected %s", n)
|
||||||
|
}
|
||||||
|
t.Root.append(n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||||
|
// installs the definition in the treeSet map. The "define" keyword has already
|
||||||
|
// been scanned.
|
||||||
|
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||||
|
const context = "define clause"
|
||||||
|
name := t.expectOneOf(itemString, itemRawString, context)
|
||||||
|
var err error
|
||||||
|
t.Name, err = strconv.Unquote(name.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
t.expect(itemRightDelim, context)
|
||||||
|
var end Node
|
||||||
|
t.Root, end = t.itemList()
|
||||||
|
if end.Type() != nodeEnd {
|
||||||
|
t.errorf("unexpected %s in %s", end, context)
|
||||||
|
}
|
||||||
|
t.add(treeSet)
|
||||||
|
t.stopParse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemList:
|
||||||
|
// textOrAction*
|
||||||
|
// Terminates at {{end}} or {{else}}, returned separately.
|
||||||
|
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||||
|
list = t.newList(t.peekNonSpace().pos)
|
||||||
|
for t.peekNonSpace().typ != itemEOF {
|
||||||
|
n := t.textOrAction()
|
||||||
|
switch n.Type() {
|
||||||
|
case nodeEnd, nodeElse:
|
||||||
|
return list, n
|
||||||
|
}
|
||||||
|
list.append(n)
|
||||||
|
}
|
||||||
|
t.errorf("unexpected EOF")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// textOrAction:
|
||||||
|
// text | action
|
||||||
|
func (t *Tree) textOrAction() Node {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemElideNewline:
|
||||||
|
return t.elideNewline()
|
||||||
|
case itemText:
|
||||||
|
return t.newText(token.pos, token.val)
|
||||||
|
case itemLeftDelim:
|
||||||
|
return t.action()
|
||||||
|
default:
|
||||||
|
t.unexpected(token, "input")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// elideNewline:
|
||||||
|
// Remove newlines trailing rightDelim if \\ is present.
|
||||||
|
func (t *Tree) elideNewline() Node {
|
||||||
|
token := t.peek()
|
||||||
|
if token.typ != itemText {
|
||||||
|
t.unexpected(token, "input")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.next()
|
||||||
|
stripped := strings.TrimLeft(token.val, "\n\r")
|
||||||
|
diff := len(token.val) - len(stripped)
|
||||||
|
if diff > 0 {
|
||||||
|
// This is a bit nasty. We mutate the token in-place to remove
|
||||||
|
// preceding newlines.
|
||||||
|
token.pos += Pos(diff)
|
||||||
|
token.val = stripped
|
||||||
|
}
|
||||||
|
return t.newText(token.pos, token.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action:
|
||||||
|
// control
|
||||||
|
// command ("|" command)*
|
||||||
|
// Left delim is past. Now get actions.
|
||||||
|
// First word could be a keyword such as range.
|
||||||
|
func (t *Tree) action() (n Node) {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemElse:
|
||||||
|
return t.elseControl()
|
||||||
|
case itemEnd:
|
||||||
|
return t.endControl()
|
||||||
|
case itemIf:
|
||||||
|
return t.ifControl()
|
||||||
|
case itemRange:
|
||||||
|
return t.rangeControl()
|
||||||
|
case itemTemplate:
|
||||||
|
return t.templateControl()
|
||||||
|
case itemWith:
|
||||||
|
return t.withControl()
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
// Do not pop variables; they persist until "end".
|
||||||
|
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline:
|
||||||
|
// declarations? command ('|' command)*
|
||||||
|
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||||
|
var decl []*VariableNode
|
||||||
|
pos := t.peekNonSpace().pos
|
||||||
|
// Are there declarations?
|
||||||
|
for {
|
||||||
|
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||||
|
t.next()
|
||||||
|
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||||
|
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||||
|
// argument variable rather than a declaration. So remember the token
|
||||||
|
// adjacent to the variable so we can push it back if necessary.
|
||||||
|
tokenAfterVariable := t.peek()
|
||||||
|
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
||||||
|
t.nextNonSpace()
|
||||||
|
variable := t.newVariable(v.pos, v.val)
|
||||||
|
decl = append(decl, variable)
|
||||||
|
t.vars = append(t.vars, v.val)
|
||||||
|
if next.typ == itemChar && next.val == "," {
|
||||||
|
if context == "range" && len(decl) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.errorf("too many declarations in %s", context)
|
||||||
|
}
|
||||||
|
} else if tokenAfterVariable.typ == itemSpace {
|
||||||
|
t.backup3(v, tokenAfterVariable)
|
||||||
|
} else {
|
||||||
|
t.backup2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
||||||
|
for {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemRightDelim, itemRightParen:
|
||||||
|
if len(pipe.Cmds) == 0 {
|
||||||
|
t.errorf("missing value for %s", context)
|
||||||
|
}
|
||||||
|
if token.typ == itemRightParen {
|
||||||
|
t.backup()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||||
|
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||||
|
t.backup()
|
||||||
|
pipe.append(t.command())
|
||||||
|
default:
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||||
|
defer t.popVars(len(t.vars))
|
||||||
|
line = t.lex.lineNumber()
|
||||||
|
pipe = t.pipeline(context)
|
||||||
|
var next Node
|
||||||
|
list, next = t.itemList()
|
||||||
|
switch next.Type() {
|
||||||
|
case nodeEnd: //done
|
||||||
|
case nodeElse:
|
||||||
|
if allowElseIf {
|
||||||
|
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||||
|
// the elseControl will have left the "if" token pending. Treat
|
||||||
|
// {{if a}}_{{else if b}}_{{end}}
|
||||||
|
// as
|
||||||
|
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||||
|
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||||
|
// is assumed. This technique works even for long if-else-if chains.
|
||||||
|
// TODO: Should we allow else-if in with and range?
|
||||||
|
if t.peek().typ == itemIf {
|
||||||
|
t.next() // Consume the "if" token.
|
||||||
|
elseList = t.newList(next.Position())
|
||||||
|
elseList.append(t.ifControl())
|
||||||
|
// Do not consume the next item - only one {{end}} required.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseList, next = t.itemList()
|
||||||
|
if next.Type() != nodeEnd {
|
||||||
|
t.errorf("expected end; found %s", next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pipe.Position(), line, pipe, list, elseList
|
||||||
|
}
|
||||||
|
|
||||||
|
// If:
|
||||||
|
// {{if pipeline}} itemList {{end}}
|
||||||
|
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// If keyword is past.
|
||||||
|
func (t *Tree) ifControl() Node {
|
||||||
|
return t.newIf(t.parseControl(true, "if"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range:
|
||||||
|
// {{range pipeline}} itemList {{end}}
|
||||||
|
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// Range keyword is past.
|
||||||
|
func (t *Tree) rangeControl() Node {
|
||||||
|
return t.newRange(t.parseControl(false, "range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// With:
|
||||||
|
// {{with pipeline}} itemList {{end}}
|
||||||
|
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// If keyword is past.
|
||||||
|
func (t *Tree) withControl() Node {
|
||||||
|
return t.newWith(t.parseControl(false, "with"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// End:
|
||||||
|
// {{end}}
|
||||||
|
// End keyword is past.
|
||||||
|
func (t *Tree) endControl() Node {
|
||||||
|
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else:
|
||||||
|
// {{else}}
|
||||||
|
// Else keyword is past.
|
||||||
|
func (t *Tree) elseControl() Node {
|
||||||
|
// Special case for "else if".
|
||||||
|
peek := t.peekNonSpace()
|
||||||
|
if peek.typ == itemIf {
|
||||||
|
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||||
|
return t.newElse(peek.pos, t.lex.lineNumber())
|
||||||
|
}
|
||||||
|
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template:
|
||||||
|
// {{template stringValue pipeline}}
|
||||||
|
// Template keyword is past. The name must be something that can evaluate
|
||||||
|
// to a string.
|
||||||
|
func (t *Tree) templateControl() Node {
|
||||||
|
var name string
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
switch token.typ {
|
||||||
|
case itemString, itemRawString:
|
||||||
|
s, err := strconv.Unquote(token.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
name = s
|
||||||
|
default:
|
||||||
|
t.unexpected(token, "template invocation")
|
||||||
|
}
|
||||||
|
var pipe *PipeNode
|
||||||
|
if t.nextNonSpace().typ != itemRightDelim {
|
||||||
|
t.backup()
|
||||||
|
// Do not pop variables; they persist until "end".
|
||||||
|
pipe = t.pipeline("template")
|
||||||
|
}
|
||||||
|
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// command:
|
||||||
|
// operand (space operand)*
|
||||||
|
// space-separated arguments up to a pipeline character or right delimiter.
|
||||||
|
// we consume the pipe character but leave the right delim to terminate the action.
|
||||||
|
func (t *Tree) command() *CommandNode {
|
||||||
|
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||||
|
for {
|
||||||
|
t.peekNonSpace() // skip leading spaces.
|
||||||
|
operand := t.operand()
|
||||||
|
if operand != nil {
|
||||||
|
cmd.append(operand)
|
||||||
|
}
|
||||||
|
switch token := t.next(); token.typ {
|
||||||
|
case itemSpace:
|
||||||
|
continue
|
||||||
|
case itemError:
|
||||||
|
t.errorf("%s", token.val)
|
||||||
|
case itemRightDelim, itemRightParen:
|
||||||
|
t.backup()
|
||||||
|
case itemPipe:
|
||||||
|
default:
|
||||||
|
t.errorf("unexpected %s in operand; missing space?", token)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(cmd.Args) == 0 {
|
||||||
|
t.errorf("empty command")
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// operand:
|
||||||
|
// term .Field*
|
||||||
|
// An operand is a space-separated component of a command,
|
||||||
|
// a term possibly followed by field accesses.
|
||||||
|
// A nil return means the next item is not an operand.
|
||||||
|
func (t *Tree) operand() Node {
|
||||||
|
node := t.term()
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if t.peek().typ == itemField {
|
||||||
|
chain := t.newChain(t.peek().pos, node)
|
||||||
|
for t.peek().typ == itemField {
|
||||||
|
chain.Add(t.next().val)
|
||||||
|
}
|
||||||
|
// Compatibility with original API: If the term is of type NodeField
|
||||||
|
// or NodeVariable, just put more fields on the original.
|
||||||
|
// Otherwise, keep the Chain node.
|
||||||
|
// TODO: Switch to Chains always when we can.
|
||||||
|
switch node.Type() {
|
||||||
|
case NodeField:
|
||||||
|
node = t.newField(chain.Position(), chain.String())
|
||||||
|
case NodeVariable:
|
||||||
|
node = t.newVariable(chain.Position(), chain.String())
|
||||||
|
default:
|
||||||
|
node = chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// term:
|
||||||
|
// literal (number, string, nil, boolean)
|
||||||
|
// function (identifier)
|
||||||
|
// .
|
||||||
|
// .Field
|
||||||
|
// $
|
||||||
|
// '(' pipeline ')'
|
||||||
|
// A term is a simple "expression".
|
||||||
|
// A nil return means the next item is not a term.
|
||||||
|
func (t *Tree) term() Node {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemError:
|
||||||
|
t.errorf("%s", token.val)
|
||||||
|
case itemIdentifier:
|
||||||
|
if !t.hasFunction(token.val) {
|
||||||
|
t.errorf("function %q not defined", token.val)
|
||||||
|
}
|
||||||
|
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||||
|
case itemDot:
|
||||||
|
return t.newDot(token.pos)
|
||||||
|
case itemNil:
|
||||||
|
return t.newNil(token.pos)
|
||||||
|
case itemVariable:
|
||||||
|
return t.useVar(token.pos, token.val)
|
||||||
|
case itemField:
|
||||||
|
return t.newField(token.pos, token.val)
|
||||||
|
case itemBool:
|
||||||
|
return t.newBool(token.pos, token.val == "true")
|
||||||
|
case itemCharConstant, itemComplex, itemNumber:
|
||||||
|
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
return number
|
||||||
|
case itemLeftParen:
|
||||||
|
pipe := t.pipeline("parenthesized pipeline")
|
||||||
|
if token := t.next(); token.typ != itemRightParen {
|
||||||
|
t.errorf("unclosed right paren: unexpected %s", token)
|
||||||
|
}
|
||||||
|
return pipe
|
||||||
|
case itemString, itemRawString:
|
||||||
|
s, err := strconv.Unquote(token.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
return t.newString(token.pos, token.val, s)
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasFunction reports if a function name exists in the Tree's maps.
|
||||||
|
func (t *Tree) hasFunction(name string) bool {
|
||||||
|
for _, funcMap := range t.funcs {
|
||||||
|
if funcMap == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if funcMap[name] != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// popVars trims the variable list to the specified length
|
||||||
|
func (t *Tree) popVars(n int) {
|
||||||
|
t.vars = t.vars[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// useVar returns a node for a variable reference. It errors if the
|
||||||
|
// variable is not defined.
|
||||||
|
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||||
|
v := t.newVariable(pos, name)
|
||||||
|
for _, varName := range t.vars {
|
||||||
|
if varName == v.Ident[0] {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.errorf("undefined variable %q", v.Ident[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// common holds the information shared by related templates.
|
||||||
|
type common struct {
|
||||||
|
tmpl map[string]*Template
|
||||||
|
// We use two maps, one for parsing and one for execution.
|
||||||
|
// This separation makes the API cleaner since it doesn't
|
||||||
|
// expose reflection to the client.
|
||||||
|
parseFuncs FuncMap
|
||||||
|
execFuncs map[string]reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template is the representation of a parsed template. The *parse.Tree
|
||||||
|
// field is exported only for use by html/template and should be treated
|
||||||
|
// as unexported by all other clients.
|
||||||
|
type Template struct {
|
||||||
|
name string
|
||||||
|
*parse.Tree
|
||||||
|
*common
|
||||||
|
leftDelim string
|
||||||
|
rightDelim string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new template with the given name.
|
||||||
|
func New(name string) *Template {
|
||||||
|
return &Template{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the template.
|
||||||
|
func (t *Template) Name() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new template associated with the given one and with the same
|
||||||
|
// delimiters. The association, which is transitive, allows one template to
|
||||||
|
// invoke another with a {{template}} action.
|
||||||
|
func (t *Template) New(name string) *Template {
|
||||||
|
t.init()
|
||||||
|
return &Template{
|
||||||
|
name: name,
|
||||||
|
common: t.common,
|
||||||
|
leftDelim: t.leftDelim,
|
||||||
|
rightDelim: t.rightDelim,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) init() {
|
||||||
|
if t.common == nil {
|
||||||
|
t.common = new(common)
|
||||||
|
t.tmpl = make(map[string]*Template)
|
||||||
|
t.parseFuncs = make(FuncMap)
|
||||||
|
t.execFuncs = make(map[string]reflect.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the template, including all associated
|
||||||
|
// templates. The actual representation is not copied, but the name space of
|
||||||
|
// associated templates is, so further calls to Parse in the copy will add
|
||||||
|
// templates to the copy but not to the original. Clone can be used to prepare
|
||||||
|
// common templates and use them with variant definitions for other templates
|
||||||
|
// by adding the variants after the clone is made.
|
||||||
|
func (t *Template) Clone() (*Template, error) {
|
||||||
|
nt := t.copy(nil)
|
||||||
|
nt.init()
|
||||||
|
nt.tmpl[t.name] = nt
|
||||||
|
for k, v := range t.tmpl {
|
||||||
|
if k == t.name { // Already installed.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The associated templates share nt's common structure.
|
||||||
|
tmpl := v.copy(nt.common)
|
||||||
|
nt.tmpl[k] = tmpl
|
||||||
|
}
|
||||||
|
for k, v := range t.parseFuncs {
|
||||||
|
nt.parseFuncs[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range t.execFuncs {
|
||||||
|
nt.execFuncs[k] = v
|
||||||
|
}
|
||||||
|
return nt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a shallow copy of t, with common set to the argument.
|
||||||
|
func (t *Template) copy(c *common) *Template {
|
||||||
|
nt := New(t.name)
|
||||||
|
nt.Tree = t.Tree
|
||||||
|
nt.common = c
|
||||||
|
nt.leftDelim = t.leftDelim
|
||||||
|
nt.rightDelim = t.rightDelim
|
||||||
|
return nt
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddParseTree creates a new template with the name and parse tree
|
||||||
|
// and associates it with t.
|
||||||
|
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||||
|
if t.common != nil && t.tmpl[name] != nil {
|
||||||
|
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
||||||
|
}
|
||||||
|
nt := t.New(name)
|
||||||
|
nt.Tree = tree
|
||||||
|
t.tmpl[name] = nt
|
||||||
|
return nt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates returns a slice of the templates associated with t, including t
|
||||||
|
// itself.
|
||||||
|
func (t *Template) Templates() []*Template {
|
||||||
|
if t.common == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Return a slice so we don't expose the map.
|
||||||
|
m := make([]*Template, 0, len(t.tmpl))
|
||||||
|
for _, v := range t.tmpl {
|
||||||
|
m = append(m, v)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delims sets the action delimiters to the specified strings, to be used in
|
||||||
|
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||||
|
// definitions will inherit the settings. An empty delimiter stands for the
|
||||||
|
// corresponding default: {{ or }}.
|
||||||
|
// The return value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Delims(left, right string) *Template {
|
||||||
|
t.leftDelim = left
|
||||||
|
t.rightDelim = right
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funcs adds the elements of the argument map to the template's function map.
|
||||||
|
// It panics if a value in the map is not a function with appropriate return
|
||||||
|
// type. However, it is legal to overwrite elements of the map. The return
|
||||||
|
// value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||||
|
t.init()
|
||||||
|
addValueFuncs(t.execFuncs, funcMap)
|
||||||
|
addFuncs(t.parseFuncs, funcMap)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns the template with the given name that is associated with t,
|
||||||
|
// or nil if there is no such template.
|
||||||
|
func (t *Template) Lookup(name string) *Template {
|
||||||
|
if t.common == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return t.tmpl[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a string into a template. Nested template definitions will be
|
||||||
|
// associated with the top-level template t. Parse may be called multiple times
|
||||||
|
// to parse definitions of templates to associate with t. It is an error if a
|
||||||
|
// resulting template is non-empty (contains content other than template
|
||||||
|
// definitions) and would replace a non-empty template with the same name.
|
||||||
|
// (In multiple calls to Parse with the same receiver template, only one call
|
||||||
|
// can contain text other than space, comments, and template definitions.)
|
||||||
|
func (t *Template) Parse(text string) (*Template, error) {
|
||||||
|
t.init()
|
||||||
|
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||||
|
for name, tree := range trees {
|
||||||
|
// If the name we parsed is the name of this template, overwrite this template.
|
||||||
|
// The associate method checks it's not a redefinition.
|
||||||
|
tmpl := t
|
||||||
|
if name != t.name {
|
||||||
|
tmpl = t.New(name)
|
||||||
|
}
|
||||||
|
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
||||||
|
if replace, err := t.associate(tmpl, tree); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if replace {
|
||||||
|
tmpl.Tree = tree
|
||||||
|
}
|
||||||
|
tmpl.leftDelim = t.leftDelim
|
||||||
|
tmpl.rightDelim = t.rightDelim
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// associate installs the new template into the group of templates associated
|
||||||
|
// with t. It is an error to reuse a name except to overwrite an empty
|
||||||
|
// template. The two are already known to share the common structure.
|
||||||
|
// The boolean return value reports wither to store this tree as t.Tree.
|
||||||
|
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
||||||
|
if new.common != t.common {
|
||||||
|
panic("internal error: associate not common")
|
||||||
|
}
|
||||||
|
name := new.name
|
||||||
|
if old := t.tmpl[name]; old != nil {
|
||||||
|
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
||||||
|
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
||||||
|
if newIsEmpty {
|
||||||
|
// Whether old is empty or not, new is empty; no reason to replace old.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if !oldIsEmpty {
|
||||||
|
return false, fmt.Errorf("template: redefinition of template %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.tmpl[name] = new
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
19
vendor/github.com/alecthomas/units/COPYING
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (C) 2014 Alec Thomas
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
11
vendor/github.com/alecthomas/units/README.md
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Units - Helpful unit multipliers and functions for Go
|
||||||
|
|
||||||
|
The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package.
|
||||||
|
|
||||||
|
It allows for code like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
n, err := ParseBase2Bytes("1KB")
|
||||||
|
// n == 1024
|
||||||
|
n = units.Mebibyte * 512
|
||||||
|
```
|
||||||
83
vendor/github.com/alecthomas/units/bytes.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte,
|
||||||
|
// etc.).
|
||||||
|
type Base2Bytes int64
|
||||||
|
|
||||||
|
// Base-2 byte units.
|
||||||
|
const (
|
||||||
|
Kibibyte Base2Bytes = 1024
|
||||||
|
KiB = Kibibyte
|
||||||
|
Mebibyte = Kibibyte * 1024
|
||||||
|
MiB = Mebibyte
|
||||||
|
Gibibyte = Mebibyte * 1024
|
||||||
|
GiB = Gibibyte
|
||||||
|
Tebibyte = Gibibyte * 1024
|
||||||
|
TiB = Tebibyte
|
||||||
|
Pebibyte = Tebibyte * 1024
|
||||||
|
PiB = Pebibyte
|
||||||
|
Exbibyte = Pebibyte * 1024
|
||||||
|
EiB = Exbibyte
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bytesUnitMap = MakeUnitMap("iB", "B", 1024)
|
||||||
|
oldBytesUnitMap = MakeUnitMap("B", "B", 1024)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB
|
||||||
|
// and KiB are both 1024.
|
||||||
|
func ParseBase2Bytes(s string) (Base2Bytes, error) {
|
||||||
|
n, err := ParseUnit(s, bytesUnitMap)
|
||||||
|
if err != nil {
|
||||||
|
n, err = ParseUnit(s, oldBytesUnitMap)
|
||||||
|
}
|
||||||
|
return Base2Bytes(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Base2Bytes) String() string {
|
||||||
|
return ToString(int64(b), 1024, "iB", "B")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricBytesUnitMap = MakeUnitMap("B", "B", 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetricBytes are SI byte units (1000 bytes in a kilobyte).
|
||||||
|
type MetricBytes SI
|
||||||
|
|
||||||
|
// SI base-10 byte units.
|
||||||
|
const (
|
||||||
|
Kilobyte MetricBytes = 1000
|
||||||
|
KB = Kilobyte
|
||||||
|
Megabyte = Kilobyte * 1000
|
||||||
|
MB = Megabyte
|
||||||
|
Gigabyte = Megabyte * 1000
|
||||||
|
GB = Gigabyte
|
||||||
|
Terabyte = Gigabyte * 1000
|
||||||
|
TB = Terabyte
|
||||||
|
Petabyte = Terabyte * 1000
|
||||||
|
PB = Petabyte
|
||||||
|
Exabyte = Petabyte * 1000
|
||||||
|
EB = Exabyte
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes.
|
||||||
|
func ParseMetricBytes(s string) (MetricBytes, error) {
|
||||||
|
n, err := ParseUnit(s, metricBytesUnitMap)
|
||||||
|
return MetricBytes(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MetricBytes) String() string {
|
||||||
|
return ToString(int64(m), 1000, "B", "B")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseStrictBytes supports both iB and B suffixes for base 2 and metric,
|
||||||
|
// respectively. That is, KiB represents 1024 and KB represents 1000.
|
||||||
|
func ParseStrictBytes(s string) (int64, error) {
|
||||||
|
n, err := ParseUnit(s, bytesUnitMap)
|
||||||
|
if err != nil {
|
||||||
|
n, err = ParseUnit(s, metricBytesUnitMap)
|
||||||
|
}
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
13
vendor/github.com/alecthomas/units/doc.go
generated
vendored
Normal 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
|
||||||
26
vendor/github.com/alecthomas/units/si.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
// SI units.
|
||||||
|
type SI int64
|
||||||
|
|
||||||
|
// SI unit multiples.
|
||||||
|
const (
|
||||||
|
Kilo SI = 1000
|
||||||
|
Mega = Kilo * 1000
|
||||||
|
Giga = Mega * 1000
|
||||||
|
Tera = Giga * 1000
|
||||||
|
Peta = Tera * 1000
|
||||||
|
Exa = Peta * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 {
|
||||||
|
return map[string]float64{
|
||||||
|
shortSuffix: 1,
|
||||||
|
"K" + suffix: float64(scale),
|
||||||
|
"M" + suffix: float64(scale * scale),
|
||||||
|
"G" + suffix: float64(scale * scale * scale),
|
||||||
|
"T" + suffix: float64(scale * scale * scale * scale),
|
||||||
|
"P" + suffix: float64(scale * scale * scale * scale * scale),
|
||||||
|
"E" + suffix: float64(scale * scale * scale * scale * scale * scale),
|
||||||
|
}
|
||||||
|
}
|
||||||
138
vendor/github.com/alecthomas/units/util.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
siUnits = []string{"", "K", "M", "G", "T", "P", "E"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToString(n int64, scale int64, suffix, baseSuffix string) string {
|
||||||
|
mn := len(siUnits)
|
||||||
|
out := make([]string, mn)
|
||||||
|
for i, m := range siUnits {
|
||||||
|
if n%scale != 0 || i == 0 && n == 0 {
|
||||||
|
s := suffix
|
||||||
|
if i == 0 {
|
||||||
|
s = baseSuffix
|
||||||
|
}
|
||||||
|
out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s)
|
||||||
|
}
|
||||||
|
n /= scale
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(out, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123
|
||||||
|
var errLeadingInt = errors.New("units: bad [0-9]*") // never printed
|
||||||
|
|
||||||
|
// leadingInt consumes the leading [0-9]* from s.
|
||||||
|
func leadingInt(s string) (x int64, rem string, err error) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if x >= (1<<63-10)/10 {
|
||||||
|
// overflow
|
||||||
|
return 0, "", errLeadingInt
|
||||||
|
}
|
||||||
|
x = x*10 + int64(c) - '0'
|
||||||
|
}
|
||||||
|
return x, s[i:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUnit(s string, unitMap map[string]float64) (int64, error) {
|
||||||
|
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
||||||
|
orig := s
|
||||||
|
f := float64(0)
|
||||||
|
neg := false
|
||||||
|
|
||||||
|
// Consume [-+]?
|
||||||
|
if s != "" {
|
||||||
|
c := s[0]
|
||||||
|
if c == '-' || c == '+' {
|
||||||
|
neg = c == '-'
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Special case: if all that is left is "0", this is zero.
|
||||||
|
if s == "0" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
for s != "" {
|
||||||
|
g := float64(0) // this element of the sequence
|
||||||
|
|
||||||
|
var x int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// The next character must be [0-9.]
|
||||||
|
if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
// Consume [0-9]*
|
||||||
|
pl := len(s)
|
||||||
|
x, s, err = leadingInt(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
g = float64(x)
|
||||||
|
pre := pl != len(s) // whether we consumed anything before a period
|
||||||
|
|
||||||
|
// Consume (\.[0-9]*)?
|
||||||
|
post := false
|
||||||
|
if s != "" && s[0] == '.' {
|
||||||
|
s = s[1:]
|
||||||
|
pl := len(s)
|
||||||
|
x, s, err = leadingInt(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
scale := 1.0
|
||||||
|
for n := pl - len(s); n > 0; n-- {
|
||||||
|
scale *= 10
|
||||||
|
}
|
||||||
|
g += float64(x) / scale
|
||||||
|
post = pl != len(s)
|
||||||
|
}
|
||||||
|
if !pre && !post {
|
||||||
|
// no digits (e.g. ".s" or "-.s")
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume unit.
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c == '.' || ('0' <= c && c <= '9') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u := s[:i]
|
||||||
|
s = s[i:]
|
||||||
|
unit, ok := unitMap[u]
|
||||||
|
if !ok {
|
||||||
|
return 0, errors.New("units: unknown unit " + u + " in " + orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
f += g * unit
|
||||||
|
}
|
||||||
|
|
||||||
|
if neg {
|
||||||
|
f = -f
|
||||||
|
}
|
||||||
|
if f < float64(-1<<63) || f > float64(1<<63-1) {
|
||||||
|
return 0, errors.New("units: overflow parsing unit")
|
||||||
|
}
|
||||||
|
return int64(f), nil
|
||||||
|
}
|
||||||
15
vendor/github.com/golang/snappy/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# This is the official list of Snappy-Go authors for copyright purposes.
|
||||||
|
# This file is distinct from the CONTRIBUTORS files.
|
||||||
|
# See the latter for an explanation.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Damian Gryski <dgryski@gmail.com>
|
||||||
|
Google Inc.
|
||||||
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||||
|
Sebastien Binet <seb.binet@gmail.com>
|
||||||
37
vendor/github.com/golang/snappy/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# This is the official list of people who can contribute
|
||||||
|
# (and typically have contributed) code to the Snappy-Go repository.
|
||||||
|
# The AUTHORS file lists the copyright holders; this file
|
||||||
|
# lists people. For example, Google employees are listed here
|
||||||
|
# but not in AUTHORS, because Google holds the copyright.
|
||||||
|
#
|
||||||
|
# The submission process automatically checks to make sure
|
||||||
|
# that people submitting code are listed in this file (by email address).
|
||||||
|
#
|
||||||
|
# Names should be added to this file only after verifying that
|
||||||
|
# the individual or the individual's organization has agreed to
|
||||||
|
# the appropriate Contributor License Agreement, found here:
|
||||||
|
#
|
||||||
|
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||||
|
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||||
|
#
|
||||||
|
# The agreement for individuals can be filled out on the web.
|
||||||
|
#
|
||||||
|
# When adding J Random Contributor's name to this file,
|
||||||
|
# either J's name or J's organization's name should be
|
||||||
|
# added to the AUTHORS file, depending on whether the
|
||||||
|
# individual or corporate CLA was used.
|
||||||
|
|
||||||
|
# Names should be added to this file like so:
|
||||||
|
# Name <email address>
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Damian Gryski <dgryski@gmail.com>
|
||||||
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Kai Backman <kaib@golang.org>
|
||||||
|
Marc-Antoine Ruel <maruel@chromium.org>
|
||||||
|
Nigel Tao <nigeltao@golang.org>
|
||||||
|
Rob Pike <r@golang.org>
|
||||||
|
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||||
|
Russ Cox <rsc@golang.org>
|
||||||
|
Sebastien Binet <seb.binet@gmail.com>
|
||||||
27
vendor/github.com/golang/snappy/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2011 The Snappy-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.
|
||||||
107
vendor/github.com/golang/snappy/README
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
The Snappy compression format in the Go programming language.
|
||||||
|
|
||||||
|
To download and install from source:
|
||||||
|
$ go get github.com/golang/snappy
|
||||||
|
|
||||||
|
Unless otherwise noted, the Snappy-Go source files are distributed
|
||||||
|
under the BSD-style license found in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Benchmarks.
|
||||||
|
|
||||||
|
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
|
||||||
|
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
|
||||||
|
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
|
||||||
|
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
|
||||||
|
|
||||||
|
"go test -test.bench=."
|
||||||
|
|
||||||
|
_UFlat0-8 2.19GB/s ± 0% html
|
||||||
|
_UFlat1-8 1.41GB/s ± 0% urls
|
||||||
|
_UFlat2-8 23.5GB/s ± 2% jpg
|
||||||
|
_UFlat3-8 1.91GB/s ± 0% jpg_200
|
||||||
|
_UFlat4-8 14.0GB/s ± 1% pdf
|
||||||
|
_UFlat5-8 1.97GB/s ± 0% html4
|
||||||
|
_UFlat6-8 814MB/s ± 0% txt1
|
||||||
|
_UFlat7-8 785MB/s ± 0% txt2
|
||||||
|
_UFlat8-8 857MB/s ± 0% txt3
|
||||||
|
_UFlat9-8 719MB/s ± 1% txt4
|
||||||
|
_UFlat10-8 2.84GB/s ± 0% pb
|
||||||
|
_UFlat11-8 1.05GB/s ± 0% gaviota
|
||||||
|
|
||||||
|
_ZFlat0-8 1.04GB/s ± 0% html
|
||||||
|
_ZFlat1-8 534MB/s ± 0% urls
|
||||||
|
_ZFlat2-8 15.7GB/s ± 1% jpg
|
||||||
|
_ZFlat3-8 740MB/s ± 3% jpg_200
|
||||||
|
_ZFlat4-8 9.20GB/s ± 1% pdf
|
||||||
|
_ZFlat5-8 991MB/s ± 0% html4
|
||||||
|
_ZFlat6-8 379MB/s ± 0% txt1
|
||||||
|
_ZFlat7-8 352MB/s ± 0% txt2
|
||||||
|
_ZFlat8-8 396MB/s ± 1% txt3
|
||||||
|
_ZFlat9-8 327MB/s ± 1% txt4
|
||||||
|
_ZFlat10-8 1.33GB/s ± 1% pb
|
||||||
|
_ZFlat11-8 605MB/s ± 1% gaviota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"go test -test.bench=. -tags=noasm"
|
||||||
|
|
||||||
|
_UFlat0-8 621MB/s ± 2% html
|
||||||
|
_UFlat1-8 494MB/s ± 1% urls
|
||||||
|
_UFlat2-8 23.2GB/s ± 1% jpg
|
||||||
|
_UFlat3-8 1.12GB/s ± 1% jpg_200
|
||||||
|
_UFlat4-8 4.35GB/s ± 1% pdf
|
||||||
|
_UFlat5-8 609MB/s ± 0% html4
|
||||||
|
_UFlat6-8 296MB/s ± 0% txt1
|
||||||
|
_UFlat7-8 288MB/s ± 0% txt2
|
||||||
|
_UFlat8-8 309MB/s ± 1% txt3
|
||||||
|
_UFlat9-8 280MB/s ± 1% txt4
|
||||||
|
_UFlat10-8 753MB/s ± 0% pb
|
||||||
|
_UFlat11-8 400MB/s ± 0% gaviota
|
||||||
|
|
||||||
|
_ZFlat0-8 409MB/s ± 1% html
|
||||||
|
_ZFlat1-8 250MB/s ± 1% urls
|
||||||
|
_ZFlat2-8 12.3GB/s ± 1% jpg
|
||||||
|
_ZFlat3-8 132MB/s ± 0% jpg_200
|
||||||
|
_ZFlat4-8 2.92GB/s ± 0% pdf
|
||||||
|
_ZFlat5-8 405MB/s ± 1% html4
|
||||||
|
_ZFlat6-8 179MB/s ± 1% txt1
|
||||||
|
_ZFlat7-8 170MB/s ± 1% txt2
|
||||||
|
_ZFlat8-8 189MB/s ± 1% txt3
|
||||||
|
_ZFlat9-8 164MB/s ± 1% txt4
|
||||||
|
_ZFlat10-8 479MB/s ± 1% pb
|
||||||
|
_ZFlat11-8 270MB/s ± 1% gaviota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
|
||||||
|
are the numbers from C++ Snappy's
|
||||||
|
|
||||||
|
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
|
||||||
|
|
||||||
|
BM_UFlat/0 2.4GB/s html
|
||||||
|
BM_UFlat/1 1.4GB/s urls
|
||||||
|
BM_UFlat/2 21.8GB/s jpg
|
||||||
|
BM_UFlat/3 1.5GB/s jpg_200
|
||||||
|
BM_UFlat/4 13.3GB/s pdf
|
||||||
|
BM_UFlat/5 2.1GB/s html4
|
||||||
|
BM_UFlat/6 1.0GB/s txt1
|
||||||
|
BM_UFlat/7 959.4MB/s txt2
|
||||||
|
BM_UFlat/8 1.0GB/s txt3
|
||||||
|
BM_UFlat/9 864.5MB/s txt4
|
||||||
|
BM_UFlat/10 2.9GB/s pb
|
||||||
|
BM_UFlat/11 1.2GB/s gaviota
|
||||||
|
|
||||||
|
BM_ZFlat/0 944.3MB/s html (22.31 %)
|
||||||
|
BM_ZFlat/1 501.6MB/s urls (47.78 %)
|
||||||
|
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
|
||||||
|
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
|
||||||
|
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
|
||||||
|
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
|
||||||
|
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
|
||||||
|
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
|
||||||
|
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
|
||||||
|
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
|
||||||
|
BM_ZFlat/10 1.2GB/s pb (19.68 %)
|
||||||
|
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
|
||||||
237
vendor/github.com/golang/snappy/decode.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// Copyright 2011 The Snappy-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 snappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCorrupt reports that the input is invalid.
|
||||||
|
ErrCorrupt = errors.New("snappy: corrupt input")
|
||||||
|
// ErrTooLarge reports that the uncompressed length is too large.
|
||||||
|
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
||||||
|
// ErrUnsupported reports that the input isn't supported.
|
||||||
|
ErrUnsupported = errors.New("snappy: unsupported input")
|
||||||
|
|
||||||
|
errUnsupportedLiteralLength = errors.New("snappy: unsupported literal length")
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodedLen returns the length of the decoded block.
|
||||||
|
func DecodedLen(src []byte) (int, error) {
|
||||||
|
v, _, err := decodedLen(src)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodedLen returns the length of the decoded block and the number of bytes
|
||||||
|
// that the length header occupied.
|
||||||
|
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
|
||||||
|
v, n := binary.Uvarint(src)
|
||||||
|
if n <= 0 || v > 0xffffffff {
|
||||||
|
return 0, 0, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
const wordSize = 32 << (^uint(0) >> 32 & 1)
|
||||||
|
if wordSize == 32 && v > 0x7fffffff {
|
||||||
|
return 0, 0, ErrTooLarge
|
||||||
|
}
|
||||||
|
return int(v), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
decodeErrCodeCorrupt = 1
|
||||||
|
decodeErrCodeUnsupportedLiteralLength = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode returns the decoded form of src. The returned slice may be a sub-
|
||||||
|
// slice of dst if dst was large enough to hold the entire decoded block.
|
||||||
|
// Otherwise, a newly allocated slice will be returned.
|
||||||
|
//
|
||||||
|
// The dst and src must not overlap. It is valid to pass a nil dst.
|
||||||
|
func Decode(dst, src []byte) ([]byte, error) {
|
||||||
|
dLen, s, err := decodedLen(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if dLen <= len(dst) {
|
||||||
|
dst = dst[:dLen]
|
||||||
|
} else {
|
||||||
|
dst = make([]byte, dLen)
|
||||||
|
}
|
||||||
|
switch decode(dst, src[s:]) {
|
||||||
|
case 0:
|
||||||
|
return dst, nil
|
||||||
|
case decodeErrCodeUnsupportedLiteralLength:
|
||||||
|
return nil, errUnsupportedLiteralLength
|
||||||
|
}
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new Reader that decompresses from r, using the framing
|
||||||
|
// format described at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
func NewReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
decoded: make([]byte, maxBlockSize),
|
||||||
|
buf: make([]byte, maxEncodedLenOfMaxBlockSize+checksumSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader is an io.Reader that can read Snappy-compressed bytes.
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
decoded []byte
|
||||||
|
buf []byte
|
||||||
|
// decoded[i:j] contains decoded bytes that have not yet been passed on.
|
||||||
|
i, j int
|
||||||
|
readHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset discards any buffered data, resets all state, and switches the Snappy
|
||||||
|
// reader to read from r. This permits reusing a Reader rather than allocating
|
||||||
|
// a new one.
|
||||||
|
func (r *Reader) Reset(reader io.Reader) {
|
||||||
|
r.r = reader
|
||||||
|
r.err = nil
|
||||||
|
r.i = 0
|
||||||
|
r.j = 0
|
||||||
|
r.readHeader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) {
|
||||||
|
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
|
||||||
|
if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read satisfies the io.Reader interface.
|
||||||
|
func (r *Reader) Read(p []byte) (int, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if r.i < r.j {
|
||||||
|
n := copy(p, r.decoded[r.i:r.j])
|
||||||
|
r.i += n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if !r.readFull(r.buf[:4], true) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
chunkType := r.buf[0]
|
||||||
|
if !r.readHeader {
|
||||||
|
if chunkType != chunkTypeStreamIdentifier {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.readHeader = true
|
||||||
|
}
|
||||||
|
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
|
||||||
|
if chunkLen > len(r.buf) {
|
||||||
|
r.err = ErrUnsupported
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chunk types are specified at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
switch chunkType {
|
||||||
|
case chunkTypeCompressedData:
|
||||||
|
// Section 4.2. Compressed data (chunk type 0x00).
|
||||||
|
if chunkLen < checksumSize {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
buf := r.buf[:chunkLen]
|
||||||
|
if !r.readFull(buf, false) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
|
buf = buf[checksumSize:]
|
||||||
|
|
||||||
|
n, err := DecodedLen(buf)
|
||||||
|
if err != nil {
|
||||||
|
r.err = err
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if n > len(r.decoded) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if _, err := Decode(r.decoded, buf); err != nil {
|
||||||
|
r.err = err
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if crc(r.decoded[:n]) != checksum {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.i, r.j = 0, n
|
||||||
|
continue
|
||||||
|
|
||||||
|
case chunkTypeUncompressedData:
|
||||||
|
// Section 4.3. Uncompressed data (chunk type 0x01).
|
||||||
|
if chunkLen < checksumSize {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
buf := r.buf[:checksumSize]
|
||||||
|
if !r.readFull(buf, false) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
|
// Read directly into r.decoded instead of via r.buf.
|
||||||
|
n := chunkLen - checksumSize
|
||||||
|
if n > len(r.decoded) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if !r.readFull(r.decoded[:n], false) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if crc(r.decoded[:n]) != checksum {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.i, r.j = 0, n
|
||||||
|
continue
|
||||||
|
|
||||||
|
case chunkTypeStreamIdentifier:
|
||||||
|
// Section 4.1. Stream identifier (chunk type 0xff).
|
||||||
|
if chunkLen != len(magicBody) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if !r.readFull(r.buf[:len(magicBody)], false) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(magicBody); i++ {
|
||||||
|
if r.buf[i] != magicBody[i] {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if chunkType <= 0x7f {
|
||||||
|
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
|
||||||
|
r.err = ErrUnsupported
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
// Section 4.4 Padding (chunk type 0xfe).
|
||||||
|
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
||||||
|
if !r.readFull(r.buf[:chunkLen], false) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
vendor/github.com/golang/snappy/decode_amd64.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2016 The Snappy-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.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
// decode has the same semantics as in decode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func decode(dst, src []byte) int
|
||||||
490
vendor/github.com/golang/snappy/decode_amd64.s
generated
vendored
Normal file
@ -0,0 +1,490 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// The asm code generally follows the pure Go code in decode_other.go, except
|
||||||
|
// where marked with a "!!!".
|
||||||
|
|
||||||
|
// func decode(dst, src []byte) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The non-zero stack size is only to
|
||||||
|
// spill registers and push args when issuing a CALL. The register allocation:
|
||||||
|
// - AX scratch
|
||||||
|
// - BX scratch
|
||||||
|
// - CX length or x
|
||||||
|
// - DX offset
|
||||||
|
// - SI &src[s]
|
||||||
|
// - DI &dst[d]
|
||||||
|
// + R8 dst_base
|
||||||
|
// + R9 dst_len
|
||||||
|
// + R10 dst_base + dst_len
|
||||||
|
// + R11 src_base
|
||||||
|
// + R12 src_len
|
||||||
|
// + R13 src_base + src_len
|
||||||
|
// - R14 used by doCopy
|
||||||
|
// - R15 used by doCopy
|
||||||
|
//
|
||||||
|
// The registers R8-R13 (marked with a "+") are set at the start of the
|
||||||
|
// function, and after a CALL returns, and are not otherwise modified.
|
||||||
|
//
|
||||||
|
// The d variable is implicitly DI - R8, and len(dst)-d is R10 - DI.
|
||||||
|
// The s variable is implicitly SI - R11, and len(src)-s is R13 - SI.
|
||||||
|
TEXT ·decode(SB), NOSPLIT, $48-56
|
||||||
|
// Initialize SI, DI and R8-R13.
|
||||||
|
MOVQ dst_base+0(FP), R8
|
||||||
|
MOVQ dst_len+8(FP), R9
|
||||||
|
MOVQ R8, DI
|
||||||
|
MOVQ R8, R10
|
||||||
|
ADDQ R9, R10
|
||||||
|
MOVQ src_base+24(FP), R11
|
||||||
|
MOVQ src_len+32(FP), R12
|
||||||
|
MOVQ R11, SI
|
||||||
|
MOVQ R11, R13
|
||||||
|
ADDQ R12, R13
|
||||||
|
|
||||||
|
loop:
|
||||||
|
// for s < len(src)
|
||||||
|
CMPQ SI, R13
|
||||||
|
JEQ end
|
||||||
|
|
||||||
|
// CX = uint32(src[s])
|
||||||
|
//
|
||||||
|
// switch src[s] & 0x03
|
||||||
|
MOVBLZX (SI), CX
|
||||||
|
MOVL CX, BX
|
||||||
|
ANDL $3, BX
|
||||||
|
CMPL BX, $1
|
||||||
|
JAE tagCopy
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// The code below handles literal tags.
|
||||||
|
|
||||||
|
// case tagLiteral:
|
||||||
|
// x := uint32(src[s] >> 2)
|
||||||
|
// switch
|
||||||
|
SHRL $2, CX
|
||||||
|
CMPL CX, $60
|
||||||
|
JAE tagLit60Plus
|
||||||
|
|
||||||
|
// case x < 60:
|
||||||
|
// s++
|
||||||
|
INCQ SI
|
||||||
|
|
||||||
|
doLit:
|
||||||
|
// This is the end of the inner "switch", when we have a literal tag.
|
||||||
|
//
|
||||||
|
// We assume that CX == x and x fits in a uint32, where x is the variable
|
||||||
|
// used in the pure Go decode_other.go code.
|
||||||
|
|
||||||
|
// length = int(x) + 1
|
||||||
|
//
|
||||||
|
// Unlike the pure Go code, we don't need to check if length <= 0 because
|
||||||
|
// CX can hold 64 bits, so the increment cannot overflow.
|
||||||
|
INCQ CX
|
||||||
|
|
||||||
|
// Prepare to check if copying length bytes will run past the end of dst or
|
||||||
|
// src.
|
||||||
|
//
|
||||||
|
// AX = len(dst) - d
|
||||||
|
// BX = len(src) - s
|
||||||
|
MOVQ R10, AX
|
||||||
|
SUBQ DI, AX
|
||||||
|
MOVQ R13, BX
|
||||||
|
SUBQ SI, BX
|
||||||
|
|
||||||
|
// !!! Try a faster technique for short (16 or fewer bytes) copies.
|
||||||
|
//
|
||||||
|
// if length > 16 || len(dst)-d < 16 || len(src)-s < 16 {
|
||||||
|
// goto callMemmove // Fall back on calling runtime·memmove.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The C++ snappy code calls this TryFastAppend. It also checks len(src)-s
|
||||||
|
// against 21 instead of 16, because it cannot assume that all of its input
|
||||||
|
// is contiguous in memory and so it needs to leave enough source bytes to
|
||||||
|
// read the next tag without refilling buffers, but Go's Decode assumes
|
||||||
|
// contiguousness (the src argument is a []byte).
|
||||||
|
CMPQ CX, $16
|
||||||
|
JGT callMemmove
|
||||||
|
CMPQ AX, $16
|
||||||
|
JLT callMemmove
|
||||||
|
CMPQ BX, $16
|
||||||
|
JLT callMemmove
|
||||||
|
|
||||||
|
// !!! Implement the copy from src to dst as a 16-byte load and store.
|
||||||
|
// (Decode's documentation says that dst and src must not overlap.)
|
||||||
|
//
|
||||||
|
// This always copies 16 bytes, instead of only length bytes, but that's
|
||||||
|
// OK. If the input is a valid Snappy encoding then subsequent iterations
|
||||||
|
// will fix up the overrun. Otherwise, Decode returns a nil []byte (and a
|
||||||
|
// non-nil error), so the overrun will be ignored.
|
||||||
|
//
|
||||||
|
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||||
|
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||||
|
// effective on architectures that are fussier about alignment.
|
||||||
|
MOVOU 0(SI), X0
|
||||||
|
MOVOU X0, 0(DI)
|
||||||
|
|
||||||
|
// d += length
|
||||||
|
// s += length
|
||||||
|
ADDQ CX, DI
|
||||||
|
ADDQ CX, SI
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
callMemmove:
|
||||||
|
// if length > len(dst)-d || length > len(src)-s { etc }
|
||||||
|
CMPQ CX, AX
|
||||||
|
JGT errCorrupt
|
||||||
|
CMPQ CX, BX
|
||||||
|
JGT errCorrupt
|
||||||
|
|
||||||
|
// copy(dst[d:], src[s:s+length])
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[d], &src[s], length), so we push
|
||||||
|
// DI, SI and CX as arguments. Coincidentally, we also need to spill those
|
||||||
|
// three registers to the stack, to save local variables across the CALL.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ SI, 8(SP)
|
||||||
|
MOVQ CX, 16(SP)
|
||||||
|
MOVQ DI, 24(SP)
|
||||||
|
MOVQ SI, 32(SP)
|
||||||
|
MOVQ CX, 40(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
|
||||||
|
// Restore local variables: unspill registers from the stack and
|
||||||
|
// re-calculate R8-R13.
|
||||||
|
MOVQ 24(SP), DI
|
||||||
|
MOVQ 32(SP), SI
|
||||||
|
MOVQ 40(SP), CX
|
||||||
|
MOVQ dst_base+0(FP), R8
|
||||||
|
MOVQ dst_len+8(FP), R9
|
||||||
|
MOVQ R8, R10
|
||||||
|
ADDQ R9, R10
|
||||||
|
MOVQ src_base+24(FP), R11
|
||||||
|
MOVQ src_len+32(FP), R12
|
||||||
|
MOVQ R11, R13
|
||||||
|
ADDQ R12, R13
|
||||||
|
|
||||||
|
// d += length
|
||||||
|
// s += length
|
||||||
|
ADDQ CX, DI
|
||||||
|
ADDQ CX, SI
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
tagLit60Plus:
|
||||||
|
// !!! This fragment does the
|
||||||
|
//
|
||||||
|
// s += x - 58; if uint(s) > uint(len(src)) { etc }
|
||||||
|
//
|
||||||
|
// checks. In the asm version, we code it once instead of once per switch case.
|
||||||
|
ADDQ CX, SI
|
||||||
|
SUBQ $58, SI
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// case x == 60:
|
||||||
|
CMPL CX, $61
|
||||||
|
JEQ tagLit61
|
||||||
|
JA tagLit62Plus
|
||||||
|
|
||||||
|
// x = uint32(src[s-1])
|
||||||
|
MOVBLZX -1(SI), CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit61:
|
||||||
|
// case x == 61:
|
||||||
|
// x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||||
|
MOVWLZX -2(SI), CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit62Plus:
|
||||||
|
CMPL CX, $62
|
||||||
|
JA tagLit63
|
||||||
|
|
||||||
|
// case x == 62:
|
||||||
|
// x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||||
|
MOVWLZX -3(SI), CX
|
||||||
|
MOVBLZX -1(SI), BX
|
||||||
|
SHLL $16, BX
|
||||||
|
ORL BX, CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit63:
|
||||||
|
// case x == 63:
|
||||||
|
// x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||||
|
MOVL -4(SI), CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
// The code above handles literal tags.
|
||||||
|
// ----------------------------------------
|
||||||
|
// The code below handles copy tags.
|
||||||
|
|
||||||
|
tagCopy4:
|
||||||
|
// case tagCopy4:
|
||||||
|
// s += 5
|
||||||
|
ADDQ $5, SI
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = 1 + int(src[s-5])>>2
|
||||||
|
SHRQ $2, CX
|
||||||
|
INCQ CX
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||||
|
MOVLQZX -4(SI), DX
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
tagCopy2:
|
||||||
|
// case tagCopy2:
|
||||||
|
// s += 3
|
||||||
|
ADDQ $3, SI
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = 1 + int(src[s-3])>>2
|
||||||
|
SHRQ $2, CX
|
||||||
|
INCQ CX
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||||
|
MOVWQZX -2(SI), DX
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
tagCopy:
|
||||||
|
// We have a copy tag. We assume that:
|
||||||
|
// - BX == src[s] & 0x03
|
||||||
|
// - CX == src[s]
|
||||||
|
CMPQ BX, $2
|
||||||
|
JEQ tagCopy2
|
||||||
|
JA tagCopy4
|
||||||
|
|
||||||
|
// case tagCopy1:
|
||||||
|
// s += 2
|
||||||
|
ADDQ $2, SI
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||||
|
MOVQ CX, DX
|
||||||
|
ANDQ $0xe0, DX
|
||||||
|
SHLQ $3, DX
|
||||||
|
MOVBQZX -1(SI), BX
|
||||||
|
ORQ BX, DX
|
||||||
|
|
||||||
|
// length = 4 + int(src[s-2])>>2&0x7
|
||||||
|
SHRQ $2, CX
|
||||||
|
ANDQ $7, CX
|
||||||
|
ADDQ $4, CX
|
||||||
|
|
||||||
|
doCopy:
|
||||||
|
// This is the end of the outer "switch", when we have a copy tag.
|
||||||
|
//
|
||||||
|
// We assume that:
|
||||||
|
// - CX == length && CX > 0
|
||||||
|
// - DX == offset
|
||||||
|
|
||||||
|
// if offset <= 0 { etc }
|
||||||
|
CMPQ DX, $0
|
||||||
|
JLE errCorrupt
|
||||||
|
|
||||||
|
// if d < offset { etc }
|
||||||
|
MOVQ DI, BX
|
||||||
|
SUBQ R8, BX
|
||||||
|
CMPQ BX, DX
|
||||||
|
JLT errCorrupt
|
||||||
|
|
||||||
|
// if length > len(dst)-d { etc }
|
||||||
|
MOVQ R10, BX
|
||||||
|
SUBQ DI, BX
|
||||||
|
CMPQ CX, BX
|
||||||
|
JGT errCorrupt
|
||||||
|
|
||||||
|
// forwardCopy(dst[d:d+length], dst[d-offset:]); d += length
|
||||||
|
//
|
||||||
|
// Set:
|
||||||
|
// - R14 = len(dst)-d
|
||||||
|
// - R15 = &dst[d-offset]
|
||||||
|
MOVQ R10, R14
|
||||||
|
SUBQ DI, R14
|
||||||
|
MOVQ DI, R15
|
||||||
|
SUBQ DX, R15
|
||||||
|
|
||||||
|
// !!! Try a faster technique for short (16 or fewer bytes) forward copies.
|
||||||
|
//
|
||||||
|
// First, try using two 8-byte load/stores, similar to the doLit technique
|
||||||
|
// above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is
|
||||||
|
// still OK if offset >= 8. Note that this has to be two 8-byte load/stores
|
||||||
|
// and not one 16-byte load/store, and the first store has to be before the
|
||||||
|
// second load, due to the overlap if offset is in the range [8, 16).
|
||||||
|
//
|
||||||
|
// if length > 16 || offset < 8 || len(dst)-d < 16 {
|
||||||
|
// goto slowForwardCopy
|
||||||
|
// }
|
||||||
|
// copy 16 bytes
|
||||||
|
// d += length
|
||||||
|
CMPQ CX, $16
|
||||||
|
JGT slowForwardCopy
|
||||||
|
CMPQ DX, $8
|
||||||
|
JLT slowForwardCopy
|
||||||
|
CMPQ R14, $16
|
||||||
|
JLT slowForwardCopy
|
||||||
|
MOVQ 0(R15), AX
|
||||||
|
MOVQ AX, 0(DI)
|
||||||
|
MOVQ 8(R15), BX
|
||||||
|
MOVQ BX, 8(DI)
|
||||||
|
ADDQ CX, DI
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
slowForwardCopy:
|
||||||
|
// !!! If the forward copy is longer than 16 bytes, or if offset < 8, we
|
||||||
|
// can still try 8-byte load stores, provided we can overrun up to 10 extra
|
||||||
|
// bytes. As above, the overrun will be fixed up by subsequent iterations
|
||||||
|
// of the outermost loop.
|
||||||
|
//
|
||||||
|
// The C++ snappy code calls this technique IncrementalCopyFastPath. Its
|
||||||
|
// commentary says:
|
||||||
|
//
|
||||||
|
// ----
|
||||||
|
//
|
||||||
|
// The main part of this loop is a simple copy of eight bytes at a time
|
||||||
|
// until we've copied (at least) the requested amount of bytes. However,
|
||||||
|
// if d and d-offset are less than eight bytes apart (indicating a
|
||||||
|
// repeating pattern of length < 8), we first need to expand the pattern in
|
||||||
|
// order to get the correct results. For instance, if the buffer looks like
|
||||||
|
// this, with the eight-byte <d-offset> and <d> patterns marked as
|
||||||
|
// intervals:
|
||||||
|
//
|
||||||
|
// abxxxxxxxxxxxx
|
||||||
|
// [------] d-offset
|
||||||
|
// [------] d
|
||||||
|
//
|
||||||
|
// a single eight-byte copy from <d-offset> to <d> will repeat the pattern
|
||||||
|
// once, after which we can move <d> two bytes without moving <d-offset>:
|
||||||
|
//
|
||||||
|
// ababxxxxxxxxxx
|
||||||
|
// [------] d-offset
|
||||||
|
// [------] d
|
||||||
|
//
|
||||||
|
// and repeat the exercise until the two no longer overlap.
|
||||||
|
//
|
||||||
|
// This allows us to do very well in the special case of one single byte
|
||||||
|
// repeated many times, without taking a big hit for more general cases.
|
||||||
|
//
|
||||||
|
// The worst case of extra writing past the end of the match occurs when
|
||||||
|
// offset == 1 and length == 1; the last copy will read from byte positions
|
||||||
|
// [0..7] and write to [4..11], whereas it was only supposed to write to
|
||||||
|
// position 1. Thus, ten excess bytes.
|
||||||
|
//
|
||||||
|
// ----
|
||||||
|
//
|
||||||
|
// That "10 byte overrun" worst case is confirmed by Go's
|
||||||
|
// TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy
|
||||||
|
// and finishSlowForwardCopy algorithm.
|
||||||
|
//
|
||||||
|
// if length > len(dst)-d-10 {
|
||||||
|
// goto verySlowForwardCopy
|
||||||
|
// }
|
||||||
|
SUBQ $10, R14
|
||||||
|
CMPQ CX, R14
|
||||||
|
JGT verySlowForwardCopy
|
||||||
|
|
||||||
|
makeOffsetAtLeast8:
|
||||||
|
// !!! As above, expand the pattern so that offset >= 8 and we can use
|
||||||
|
// 8-byte load/stores.
|
||||||
|
//
|
||||||
|
// for offset < 8 {
|
||||||
|
// copy 8 bytes from dst[d-offset:] to dst[d:]
|
||||||
|
// length -= offset
|
||||||
|
// d += offset
|
||||||
|
// offset += offset
|
||||||
|
// // The two previous lines together means that d-offset, and therefore
|
||||||
|
// // R15, is unchanged.
|
||||||
|
// }
|
||||||
|
CMPQ DX, $8
|
||||||
|
JGE fixUpSlowForwardCopy
|
||||||
|
MOVQ (R15), BX
|
||||||
|
MOVQ BX, (DI)
|
||||||
|
SUBQ DX, CX
|
||||||
|
ADDQ DX, DI
|
||||||
|
ADDQ DX, DX
|
||||||
|
JMP makeOffsetAtLeast8
|
||||||
|
|
||||||
|
fixUpSlowForwardCopy:
|
||||||
|
// !!! Add length (which might be negative now) to d (implied by DI being
|
||||||
|
// &dst[d]) so that d ends up at the right place when we jump back to the
|
||||||
|
// top of the loop. Before we do that, though, we save DI to AX so that, if
|
||||||
|
// length is positive, copying the remaining length bytes will write to the
|
||||||
|
// right place.
|
||||||
|
MOVQ DI, AX
|
||||||
|
ADDQ CX, DI
|
||||||
|
|
||||||
|
finishSlowForwardCopy:
|
||||||
|
// !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative
|
||||||
|
// length means that we overrun, but as above, that will be fixed up by
|
||||||
|
// subsequent iterations of the outermost loop.
|
||||||
|
CMPQ CX, $0
|
||||||
|
JLE loop
|
||||||
|
MOVQ (R15), BX
|
||||||
|
MOVQ BX, (AX)
|
||||||
|
ADDQ $8, R15
|
||||||
|
ADDQ $8, AX
|
||||||
|
SUBQ $8, CX
|
||||||
|
JMP finishSlowForwardCopy
|
||||||
|
|
||||||
|
verySlowForwardCopy:
|
||||||
|
// verySlowForwardCopy is a simple implementation of forward copy. In C
|
||||||
|
// parlance, this is a do/while loop instead of a while loop, since we know
|
||||||
|
// that length > 0. In Go syntax:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// dst[d] = dst[d - offset]
|
||||||
|
// d++
|
||||||
|
// length--
|
||||||
|
// if length == 0 {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
MOVB (R15), BX
|
||||||
|
MOVB BX, (DI)
|
||||||
|
INCQ R15
|
||||||
|
INCQ DI
|
||||||
|
DECQ CX
|
||||||
|
JNZ verySlowForwardCopy
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
// The code above handles copy tags.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
end:
|
||||||
|
// This is the end of the "for s < len(src)".
|
||||||
|
//
|
||||||
|
// if d != len(dst) { etc }
|
||||||
|
CMPQ DI, R10
|
||||||
|
JNE errCorrupt
|
||||||
|
|
||||||
|
// return 0
|
||||||
|
MOVQ $0, ret+48(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
errCorrupt:
|
||||||
|
// return decodeErrCodeCorrupt
|
||||||
|
MOVQ $1, ret+48(FP)
|
||||||
|
RET
|
||||||
101
vendor/github.com/golang/snappy/decode_other.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2016 The Snappy-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.
|
||||||
|
|
||||||
|
// +build !amd64 appengine !gc noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
// decode writes the decoding of src to dst. It assumes that the varint-encoded
|
||||||
|
// length of the decompressed bytes has already been read, and that len(dst)
|
||||||
|
// equals that length.
|
||||||
|
//
|
||||||
|
// It returns 0 on success or a decodeErrCodeXxx error code on failure.
|
||||||
|
func decode(dst, src []byte) int {
|
||||||
|
var d, s, offset, length int
|
||||||
|
for s < len(src) {
|
||||||
|
switch src[s] & 0x03 {
|
||||||
|
case tagLiteral:
|
||||||
|
x := uint32(src[s] >> 2)
|
||||||
|
switch {
|
||||||
|
case x < 60:
|
||||||
|
s++
|
||||||
|
case x == 60:
|
||||||
|
s += 2
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-1])
|
||||||
|
case x == 61:
|
||||||
|
s += 3
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||||
|
case x == 62:
|
||||||
|
s += 4
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||||
|
case x == 63:
|
||||||
|
s += 5
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||||
|
}
|
||||||
|
length = int(x) + 1
|
||||||
|
if length <= 0 {
|
||||||
|
return decodeErrCodeUnsupportedLiteralLength
|
||||||
|
}
|
||||||
|
if length > len(dst)-d || length > len(src)-s {
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
copy(dst[d:], src[s:s+length])
|
||||||
|
d += length
|
||||||
|
s += length
|
||||||
|
continue
|
||||||
|
|
||||||
|
case tagCopy1:
|
||||||
|
s += 2
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
length = 4 + int(src[s-2])>>2&0x7
|
||||||
|
offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||||
|
|
||||||
|
case tagCopy2:
|
||||||
|
s += 3
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
length = 1 + int(src[s-3])>>2
|
||||||
|
offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||||
|
|
||||||
|
case tagCopy4:
|
||||||
|
s += 5
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
length = 1 + int(src[s-5])>>2
|
||||||
|
offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset <= 0 || d < offset || length > len(dst)-d {
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
// Copy from an earlier sub-slice of dst to a later sub-slice. Unlike
|
||||||
|
// the built-in copy function, this byte-by-byte copy always runs
|
||||||
|
// forwards, even if the slices overlap. Conceptually, this is:
|
||||||
|
//
|
||||||
|
// d += forwardCopy(dst[d:d+length], dst[d-offset:])
|
||||||
|
for end := d + length; d != end; d++ {
|
||||||
|
dst[d] = dst[d-offset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d != len(dst) {
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
285
vendor/github.com/golang/snappy/encode.go
generated
vendored
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
// Copyright 2011 The Snappy-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 snappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode returns the encoded form of src. The returned slice may be a sub-
|
||||||
|
// slice of dst if dst was large enough to hold the entire encoded block.
|
||||||
|
// Otherwise, a newly allocated slice will be returned.
|
||||||
|
//
|
||||||
|
// The dst and src must not overlap. It is valid to pass a nil dst.
|
||||||
|
func Encode(dst, src []byte) []byte {
|
||||||
|
if n := MaxEncodedLen(len(src)); n < 0 {
|
||||||
|
panic(ErrTooLarge)
|
||||||
|
} else if len(dst) < n {
|
||||||
|
dst = make([]byte, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block starts with the varint-encoded length of the decompressed bytes.
|
||||||
|
d := binary.PutUvarint(dst, uint64(len(src)))
|
||||||
|
|
||||||
|
for len(src) > 0 {
|
||||||
|
p := src
|
||||||
|
src = nil
|
||||||
|
if len(p) > maxBlockSize {
|
||||||
|
p, src = p[:maxBlockSize], p[maxBlockSize:]
|
||||||
|
}
|
||||||
|
if len(p) < minNonLiteralBlockSize {
|
||||||
|
d += emitLiteral(dst[d:], p)
|
||||||
|
} else {
|
||||||
|
d += encodeBlock(dst[d:], p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst[:d]
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputMargin is the minimum number of extra input bytes to keep, inside
|
||||||
|
// encodeBlock's inner loop. On some architectures, this margin lets us
|
||||||
|
// implement a fast path for emitLiteral, where the copy of short (<= 16 byte)
|
||||||
|
// literals can be implemented as a single load to and store from a 16-byte
|
||||||
|
// register. That literal's actual length can be as short as 1 byte, so this
|
||||||
|
// can copy up to 15 bytes too much, but that's OK as subsequent iterations of
|
||||||
|
// the encoding loop will fix up the copy overrun, and this inputMargin ensures
|
||||||
|
// that we don't overrun the dst and src buffers.
|
||||||
|
const inputMargin = 16 - 1
|
||||||
|
|
||||||
|
// minNonLiteralBlockSize is the minimum size of the input to encodeBlock that
|
||||||
|
// could be encoded with a copy tag. This is the minimum with respect to the
|
||||||
|
// algorithm used by encodeBlock, not a minimum enforced by the file format.
|
||||||
|
//
|
||||||
|
// The encoded output must start with at least a 1 byte literal, as there are
|
||||||
|
// no previous bytes to copy. A minimal (1 byte) copy after that, generated
|
||||||
|
// from an emitCopy call in encodeBlock's main loop, would require at least
|
||||||
|
// another inputMargin bytes, for the reason above: we want any emitLiteral
|
||||||
|
// calls inside encodeBlock's main loop to use the fast path if possible, which
|
||||||
|
// requires being able to overrun by inputMargin bytes. Thus,
|
||||||
|
// minNonLiteralBlockSize equals 1 + 1 + inputMargin.
|
||||||
|
//
|
||||||
|
// The C++ code doesn't use this exact threshold, but it could, as discussed at
|
||||||
|
// https://groups.google.com/d/topic/snappy-compression/oGbhsdIJSJ8/discussion
|
||||||
|
// The difference between Go (2+inputMargin) and C++ (inputMargin) is purely an
|
||||||
|
// optimization. It should not affect the encoded form. This is tested by
|
||||||
|
// TestSameEncodingAsCppShortCopies.
|
||||||
|
const minNonLiteralBlockSize = 1 + 1 + inputMargin
|
||||||
|
|
||||||
|
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
||||||
|
// uncompressed length.
|
||||||
|
//
|
||||||
|
// It will return a negative value if srcLen is too large to encode.
|
||||||
|
func MaxEncodedLen(srcLen int) int {
|
||||||
|
n := uint64(srcLen)
|
||||||
|
if n > 0xffffffff {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// Compressed data can be defined as:
|
||||||
|
// compressed := item* literal*
|
||||||
|
// item := literal* copy
|
||||||
|
//
|
||||||
|
// The trailing literal sequence has a space blowup of at most 62/60
|
||||||
|
// since a literal of length 60 needs one tag byte + one extra byte
|
||||||
|
// for length information.
|
||||||
|
//
|
||||||
|
// Item blowup is trickier to measure. Suppose the "copy" op copies
|
||||||
|
// 4 bytes of data. Because of a special check in the encoding code,
|
||||||
|
// we produce a 4-byte copy only if the offset is < 65536. Therefore
|
||||||
|
// the copy op takes 3 bytes to encode, and this type of item leads
|
||||||
|
// to at most the 62/60 blowup for representing literals.
|
||||||
|
//
|
||||||
|
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
|
||||||
|
// enough, it will take 5 bytes to encode the copy op. Therefore the
|
||||||
|
// worst case here is a one-byte literal followed by a five-byte copy.
|
||||||
|
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
|
||||||
|
//
|
||||||
|
// This last factor dominates the blowup, so the final estimate is:
|
||||||
|
n = 32 + n + n/6
|
||||||
|
if n > 0xffffffff {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errClosed = errors.New("snappy: Writer is closed")
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer that compresses to w.
|
||||||
|
//
|
||||||
|
// The Writer returned does not buffer writes. There is no need to Flush or
|
||||||
|
// Close such a Writer.
|
||||||
|
//
|
||||||
|
// Deprecated: the Writer returned is not suitable for many small writes, only
|
||||||
|
// for few large writes. Use NewBufferedWriter instead, which is efficient
|
||||||
|
// regardless of the frequency and shape of the writes, and remember to Close
|
||||||
|
// that Writer when done.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
obuf: make([]byte, obufLen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferedWriter returns a new Writer that compresses to w, using the
|
||||||
|
// framing format described at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
//
|
||||||
|
// The Writer returned buffers writes. Users must call Close to guarantee all
|
||||||
|
// data has been forwarded to the underlying io.Writer. They may also call
|
||||||
|
// Flush zero or more times before calling Close.
|
||||||
|
func NewBufferedWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
ibuf: make([]byte, 0, maxBlockSize),
|
||||||
|
obuf: make([]byte, obufLen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is an io.Writer that can write Snappy-compressed bytes.
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
|
||||||
|
// ibuf is a buffer for the incoming (uncompressed) bytes.
|
||||||
|
//
|
||||||
|
// Its use is optional. For backwards compatibility, Writers created by the
|
||||||
|
// NewWriter function have ibuf == nil, do not buffer incoming bytes, and
|
||||||
|
// therefore do not need to be Flush'ed or Close'd.
|
||||||
|
ibuf []byte
|
||||||
|
|
||||||
|
// obuf is a buffer for the outgoing (compressed) bytes.
|
||||||
|
obuf []byte
|
||||||
|
|
||||||
|
// wroteStreamHeader is whether we have written the stream header.
|
||||||
|
wroteStreamHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset discards the writer's state and switches the Snappy writer to write to
|
||||||
|
// w. This permits reusing a Writer rather than allocating a new one.
|
||||||
|
func (w *Writer) Reset(writer io.Writer) {
|
||||||
|
w.w = writer
|
||||||
|
w.err = nil
|
||||||
|
if w.ibuf != nil {
|
||||||
|
w.ibuf = w.ibuf[:0]
|
||||||
|
}
|
||||||
|
w.wroteStreamHeader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write satisfies the io.Writer interface.
|
||||||
|
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
|
||||||
|
if w.ibuf == nil {
|
||||||
|
// Do not buffer incoming bytes. This does not perform or compress well
|
||||||
|
// if the caller of Writer.Write writes many small slices. This
|
||||||
|
// behavior is therefore deprecated, but still supported for backwards
|
||||||
|
// compatibility with code that doesn't explicitly Flush or Close.
|
||||||
|
return w.write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The remainder of this method is based on bufio.Writer.Write from the
|
||||||
|
// standard library.
|
||||||
|
|
||||||
|
for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err == nil {
|
||||||
|
var n int
|
||||||
|
if len(w.ibuf) == 0 {
|
||||||
|
// Large write, empty buffer.
|
||||||
|
// Write directly from p to avoid copy.
|
||||||
|
n, _ = w.write(p)
|
||||||
|
} else {
|
||||||
|
n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||||
|
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
nRet += n
|
||||||
|
p = p[n:]
|
||||||
|
}
|
||||||
|
if w.err != nil {
|
||||||
|
return nRet, w.err
|
||||||
|
}
|
||||||
|
n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||||
|
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||||
|
nRet += n
|
||||||
|
return nRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) write(p []byte) (nRet int, errRet error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
for len(p) > 0 {
|
||||||
|
obufStart := len(magicChunk)
|
||||||
|
if !w.wroteStreamHeader {
|
||||||
|
w.wroteStreamHeader = true
|
||||||
|
copy(w.obuf, magicChunk)
|
||||||
|
obufStart = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var uncompressed []byte
|
||||||
|
if len(p) > maxBlockSize {
|
||||||
|
uncompressed, p = p[:maxBlockSize], p[maxBlockSize:]
|
||||||
|
} else {
|
||||||
|
uncompressed, p = p, nil
|
||||||
|
}
|
||||||
|
checksum := crc(uncompressed)
|
||||||
|
|
||||||
|
// Compress the buffer, discarding the result if the improvement
|
||||||
|
// isn't at least 12.5%.
|
||||||
|
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
|
||||||
|
chunkType := uint8(chunkTypeCompressedData)
|
||||||
|
chunkLen := 4 + len(compressed)
|
||||||
|
obufEnd := obufHeaderLen + len(compressed)
|
||||||
|
if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
|
||||||
|
chunkType = chunkTypeUncompressedData
|
||||||
|
chunkLen = 4 + len(uncompressed)
|
||||||
|
obufEnd = obufHeaderLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the per-chunk header that comes before the body.
|
||||||
|
w.obuf[len(magicChunk)+0] = chunkType
|
||||||
|
w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
|
||||||
|
w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
|
||||||
|
w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
|
||||||
|
w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
|
||||||
|
w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
|
||||||
|
w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
|
||||||
|
w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
|
||||||
|
|
||||||
|
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return nRet, err
|
||||||
|
}
|
||||||
|
if chunkType == chunkTypeUncompressedData {
|
||||||
|
if _, err := w.w.Write(uncompressed); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return nRet, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nRet += len(uncompressed)
|
||||||
|
}
|
||||||
|
return nRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes the Writer to its underlying io.Writer.
|
||||||
|
func (w *Writer) Flush() error {
|
||||||
|
if w.err != nil {
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
if len(w.ibuf) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.write(w.ibuf)
|
||||||
|
w.ibuf = w.ibuf[:0]
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close calls Flush and then closes the Writer.
|
||||||
|
func (w *Writer) Close() error {
|
||||||
|
w.Flush()
|
||||||
|
ret := w.err
|
||||||
|
if w.err == nil {
|
||||||
|
w.err = errClosed
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
29
vendor/github.com/golang/snappy/encode_amd64.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2016 The Snappy-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.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
// emitLiteral has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func emitLiteral(dst, lit []byte) int
|
||||||
|
|
||||||
|
// emitCopy has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func emitCopy(dst []byte, offset, length int) int
|
||||||
|
|
||||||
|
// extendMatch has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func extendMatch(src []byte, i, j int) int
|
||||||
|
|
||||||
|
// encodeBlock has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func encodeBlock(dst, src []byte) (d int)
|
||||||
730
vendor/github.com/golang/snappy/encode_amd64.s
generated
vendored
Normal file
@ -0,0 +1,730 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// The XXX lines assemble on Go 1.4, 1.5 and 1.7, but not 1.6, due to a
|
||||||
|
// Go toolchain regression. See https://github.com/golang/go/issues/15426 and
|
||||||
|
// https://github.com/golang/snappy/issues/29
|
||||||
|
//
|
||||||
|
// As a workaround, the package was built with a known good assembler, and
|
||||||
|
// those instructions were disassembled by "objdump -d" to yield the
|
||||||
|
// 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||||
|
// style comments, in AT&T asm syntax. Note that rsp here is a physical
|
||||||
|
// register, not Go/asm's SP pseudo-register (see https://golang.org/doc/asm).
|
||||||
|
// The instructions were then encoded as "BYTE $0x.." sequences, which assemble
|
||||||
|
// fine on Go 1.6.
|
||||||
|
|
||||||
|
// The asm code generally follows the pure Go code in encode_other.go, except
|
||||||
|
// where marked with a "!!!".
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func emitLiteral(dst, lit []byte) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The register allocation:
|
||||||
|
// - AX len(lit)
|
||||||
|
// - BX n
|
||||||
|
// - DX return value
|
||||||
|
// - DI &dst[i]
|
||||||
|
// - R10 &lit[0]
|
||||||
|
//
|
||||||
|
// The 24 bytes of stack space is to call runtime·memmove.
|
||||||
|
//
|
||||||
|
// The unusual register allocation of local variables, such as R10 for the
|
||||||
|
// source pointer, matches the allocation used at the call site in encodeBlock,
|
||||||
|
// which makes it easier to manually inline this function.
|
||||||
|
TEXT ·emitLiteral(SB), NOSPLIT, $24-56
|
||||||
|
MOVQ dst_base+0(FP), DI
|
||||||
|
MOVQ lit_base+24(FP), R10
|
||||||
|
MOVQ lit_len+32(FP), AX
|
||||||
|
MOVQ AX, DX
|
||||||
|
MOVL AX, BX
|
||||||
|
SUBL $1, BX
|
||||||
|
|
||||||
|
CMPL BX, $60
|
||||||
|
JLT oneByte
|
||||||
|
CMPL BX, $256
|
||||||
|
JLT twoBytes
|
||||||
|
|
||||||
|
threeBytes:
|
||||||
|
MOVB $0xf4, 0(DI)
|
||||||
|
MOVW BX, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
ADDQ $3, DX
|
||||||
|
JMP memmove
|
||||||
|
|
||||||
|
twoBytes:
|
||||||
|
MOVB $0xf0, 0(DI)
|
||||||
|
MOVB BX, 1(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
ADDQ $2, DX
|
||||||
|
JMP memmove
|
||||||
|
|
||||||
|
oneByte:
|
||||||
|
SHLB $2, BX
|
||||||
|
MOVB BX, 0(DI)
|
||||||
|
ADDQ $1, DI
|
||||||
|
ADDQ $1, DX
|
||||||
|
|
||||||
|
memmove:
|
||||||
|
MOVQ DX, ret+48(FP)
|
||||||
|
|
||||||
|
// copy(dst[i:], lit)
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
||||||
|
// DI, R10 and AX as arguments.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ R10, 8(SP)
|
||||||
|
MOVQ AX, 16(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func emitCopy(dst []byte, offset, length int) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The register allocation:
|
||||||
|
// - AX length
|
||||||
|
// - SI &dst[0]
|
||||||
|
// - DI &dst[i]
|
||||||
|
// - R11 offset
|
||||||
|
//
|
||||||
|
// The unusual register allocation of local variables, such as R11 for the
|
||||||
|
// offset, matches the allocation used at the call site in encodeBlock, which
|
||||||
|
// makes it easier to manually inline this function.
|
||||||
|
TEXT ·emitCopy(SB), NOSPLIT, $0-48
|
||||||
|
MOVQ dst_base+0(FP), DI
|
||||||
|
MOVQ DI, SI
|
||||||
|
MOVQ offset+24(FP), R11
|
||||||
|
MOVQ length+32(FP), AX
|
||||||
|
|
||||||
|
loop0:
|
||||||
|
// for length >= 68 { etc }
|
||||||
|
CMPL AX, $68
|
||||||
|
JLT step1
|
||||||
|
|
||||||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xfe, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $64, AX
|
||||||
|
JMP loop0
|
||||||
|
|
||||||
|
step1:
|
||||||
|
// if length > 64 { etc }
|
||||||
|
CMPL AX, $64
|
||||||
|
JLE step2
|
||||||
|
|
||||||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xee, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $60, AX
|
||||||
|
|
||||||
|
step2:
|
||||||
|
// if length >= 12 || offset >= 2048 { goto step3 }
|
||||||
|
CMPL AX, $12
|
||||||
|
JGE step3
|
||||||
|
CMPL R11, $2048
|
||||||
|
JGE step3
|
||||||
|
|
||||||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||||||
|
MOVB R11, 1(DI)
|
||||||
|
SHRL $8, R11
|
||||||
|
SHLB $5, R11
|
||||||
|
SUBB $4, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB AX, R11
|
||||||
|
ORB $1, R11
|
||||||
|
MOVB R11, 0(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
|
||||||
|
// Return the number of bytes written.
|
||||||
|
SUBQ SI, DI
|
||||||
|
MOVQ DI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
step3:
|
||||||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||||||
|
SUBL $1, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB $2, AX
|
||||||
|
MOVB AX, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
|
||||||
|
// Return the number of bytes written.
|
||||||
|
SUBQ SI, DI
|
||||||
|
MOVQ DI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func extendMatch(src []byte, i, j int) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The register allocation:
|
||||||
|
// - DX &src[0]
|
||||||
|
// - SI &src[j]
|
||||||
|
// - R13 &src[len(src) - 8]
|
||||||
|
// - R14 &src[len(src)]
|
||||||
|
// - R15 &src[i]
|
||||||
|
//
|
||||||
|
// The unusual register allocation of local variables, such as R15 for a source
|
||||||
|
// pointer, matches the allocation used at the call site in encodeBlock, which
|
||||||
|
// makes it easier to manually inline this function.
|
||||||
|
TEXT ·extendMatch(SB), NOSPLIT, $0-48
|
||||||
|
MOVQ src_base+0(FP), DX
|
||||||
|
MOVQ src_len+8(FP), R14
|
||||||
|
MOVQ i+24(FP), R15
|
||||||
|
MOVQ j+32(FP), SI
|
||||||
|
ADDQ DX, R14
|
||||||
|
ADDQ DX, R15
|
||||||
|
ADDQ DX, SI
|
||||||
|
MOVQ R14, R13
|
||||||
|
SUBQ $8, R13
|
||||||
|
|
||||||
|
cmp8:
|
||||||
|
// As long as we are 8 or more bytes before the end of src, we can load and
|
||||||
|
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
||||||
|
CMPQ SI, R13
|
||||||
|
JA cmp1
|
||||||
|
MOVQ (R15), AX
|
||||||
|
MOVQ (SI), BX
|
||||||
|
CMPQ AX, BX
|
||||||
|
JNE bsf
|
||||||
|
ADDQ $8, R15
|
||||||
|
ADDQ $8, SI
|
||||||
|
JMP cmp8
|
||||||
|
|
||||||
|
bsf:
|
||||||
|
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
||||||
|
// the index of the first byte that differs. The BSF instruction finds the
|
||||||
|
// least significant 1 bit, the amd64 architecture is little-endian, and
|
||||||
|
// the shift by 3 converts a bit index to a byte index.
|
||||||
|
XORQ AX, BX
|
||||||
|
BSFQ BX, BX
|
||||||
|
SHRQ $3, BX
|
||||||
|
ADDQ BX, SI
|
||||||
|
|
||||||
|
// Convert from &src[ret] to ret.
|
||||||
|
SUBQ DX, SI
|
||||||
|
MOVQ SI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
cmp1:
|
||||||
|
// In src's tail, compare 1 byte at a time.
|
||||||
|
CMPQ SI, R14
|
||||||
|
JAE extendMatchEnd
|
||||||
|
MOVB (R15), AX
|
||||||
|
MOVB (SI), BX
|
||||||
|
CMPB AX, BX
|
||||||
|
JNE extendMatchEnd
|
||||||
|
ADDQ $1, R15
|
||||||
|
ADDQ $1, SI
|
||||||
|
JMP cmp1
|
||||||
|
|
||||||
|
extendMatchEnd:
|
||||||
|
// Convert from &src[ret] to ret.
|
||||||
|
SUBQ DX, SI
|
||||||
|
MOVQ SI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func encodeBlock(dst, src []byte) (d int)
|
||||||
|
//
|
||||||
|
// All local variables fit into registers, other than "var table". The register
|
||||||
|
// allocation:
|
||||||
|
// - AX . .
|
||||||
|
// - BX . .
|
||||||
|
// - CX 56 shift (note that amd64 shifts by non-immediates must use CX).
|
||||||
|
// - DX 64 &src[0], tableSize
|
||||||
|
// - SI 72 &src[s]
|
||||||
|
// - DI 80 &dst[d]
|
||||||
|
// - R9 88 sLimit
|
||||||
|
// - R10 . &src[nextEmit]
|
||||||
|
// - R11 96 prevHash, currHash, nextHash, offset
|
||||||
|
// - R12 104 &src[base], skip
|
||||||
|
// - R13 . &src[nextS], &src[len(src) - 8]
|
||||||
|
// - R14 . len(src), bytesBetweenHashLookups, &src[len(src)], x
|
||||||
|
// - R15 112 candidate
|
||||||
|
//
|
||||||
|
// The second column (56, 64, etc) is the stack offset to spill the registers
|
||||||
|
// when calling other functions. We could pack this slightly tighter, but it's
|
||||||
|
// simpler to have a dedicated spill map independent of the function called.
|
||||||
|
//
|
||||||
|
// "var table [maxTableSize]uint16" takes up 32768 bytes of stack space. An
|
||||||
|
// extra 56 bytes, to call other functions, and an extra 64 bytes, to spill
|
||||||
|
// local variables (registers) during calls gives 32768 + 56 + 64 = 32888.
|
||||||
|
TEXT ·encodeBlock(SB), 0, $32888-56
|
||||||
|
MOVQ dst_base+0(FP), DI
|
||||||
|
MOVQ src_base+24(FP), SI
|
||||||
|
MOVQ src_len+32(FP), R14
|
||||||
|
|
||||||
|
// shift, tableSize := uint32(32-8), 1<<8
|
||||||
|
MOVQ $24, CX
|
||||||
|
MOVQ $256, DX
|
||||||
|
|
||||||
|
calcShift:
|
||||||
|
// for ; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
||||||
|
// shift--
|
||||||
|
// }
|
||||||
|
CMPQ DX, $16384
|
||||||
|
JGE varTable
|
||||||
|
CMPQ DX, R14
|
||||||
|
JGE varTable
|
||||||
|
SUBQ $1, CX
|
||||||
|
SHLQ $1, DX
|
||||||
|
JMP calcShift
|
||||||
|
|
||||||
|
varTable:
|
||||||
|
// var table [maxTableSize]uint16
|
||||||
|
//
|
||||||
|
// In the asm code, unlike the Go code, we can zero-initialize only the
|
||||||
|
// first tableSize elements. Each uint16 element is 2 bytes and each MOVOU
|
||||||
|
// writes 16 bytes, so we can do only tableSize/8 writes instead of the
|
||||||
|
// 2048 writes that would zero-initialize all of table's 32768 bytes.
|
||||||
|
SHRQ $3, DX
|
||||||
|
LEAQ table-32768(SP), BX
|
||||||
|
PXOR X0, X0
|
||||||
|
|
||||||
|
memclr:
|
||||||
|
MOVOU X0, 0(BX)
|
||||||
|
ADDQ $16, BX
|
||||||
|
SUBQ $1, DX
|
||||||
|
JNZ memclr
|
||||||
|
|
||||||
|
// !!! DX = &src[0]
|
||||||
|
MOVQ SI, DX
|
||||||
|
|
||||||
|
// sLimit := len(src) - inputMargin
|
||||||
|
MOVQ R14, R9
|
||||||
|
SUBQ $15, R9
|
||||||
|
|
||||||
|
// !!! Pre-emptively spill CX, DX and R9 to the stack. Their values don't
|
||||||
|
// change for the rest of the function.
|
||||||
|
MOVQ CX, 56(SP)
|
||||||
|
MOVQ DX, 64(SP)
|
||||||
|
MOVQ R9, 88(SP)
|
||||||
|
|
||||||
|
// nextEmit := 0
|
||||||
|
MOVQ DX, R10
|
||||||
|
|
||||||
|
// s := 1
|
||||||
|
ADDQ $1, SI
|
||||||
|
|
||||||
|
// nextHash := hash(load32(src, s), shift)
|
||||||
|
MOVL 0(SI), R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
outer:
|
||||||
|
// for { etc }
|
||||||
|
|
||||||
|
// skip := 32
|
||||||
|
MOVQ $32, R12
|
||||||
|
|
||||||
|
// nextS := s
|
||||||
|
MOVQ SI, R13
|
||||||
|
|
||||||
|
// candidate := 0
|
||||||
|
MOVQ $0, R15
|
||||||
|
|
||||||
|
inner0:
|
||||||
|
// for { etc }
|
||||||
|
|
||||||
|
// s := nextS
|
||||||
|
MOVQ R13, SI
|
||||||
|
|
||||||
|
// bytesBetweenHashLookups := skip >> 5
|
||||||
|
MOVQ R12, R14
|
||||||
|
SHRQ $5, R14
|
||||||
|
|
||||||
|
// nextS = s + bytesBetweenHashLookups
|
||||||
|
ADDQ R14, R13
|
||||||
|
|
||||||
|
// skip += bytesBetweenHashLookups
|
||||||
|
ADDQ R14, R12
|
||||||
|
|
||||||
|
// if nextS > sLimit { goto emitRemainder }
|
||||||
|
MOVQ R13, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
CMPQ AX, R9
|
||||||
|
JA emitRemainder
|
||||||
|
|
||||||
|
// candidate = int(table[nextHash])
|
||||||
|
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
||||||
|
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||||
|
BYTE $0x4e
|
||||||
|
BYTE $0x0f
|
||||||
|
BYTE $0xb7
|
||||||
|
BYTE $0x7c
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// table[nextHash] = uint16(s)
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
|
||||||
|
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||||
|
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||||
|
BYTE $0x66
|
||||||
|
BYTE $0x42
|
||||||
|
BYTE $0x89
|
||||||
|
BYTE $0x44
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// nextHash = hash(load32(src, nextS), shift)
|
||||||
|
MOVL 0(R13), R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// if load32(src, s) != load32(src, candidate) { continue } break
|
||||||
|
MOVL 0(SI), AX
|
||||||
|
MOVL (DX)(R15*1), BX
|
||||||
|
CMPL AX, BX
|
||||||
|
JNE inner0
|
||||||
|
|
||||||
|
fourByteMatch:
|
||||||
|
// As per the encode_other.go code:
|
||||||
|
//
|
||||||
|
// A 4-byte match has been found. We'll later see etc.
|
||||||
|
|
||||||
|
// !!! Jump to a fast path for short (<= 16 byte) literals. See the comment
|
||||||
|
// on inputMargin in encode.go.
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ R10, AX
|
||||||
|
CMPQ AX, $16
|
||||||
|
JLE emitLiteralFastPath
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Begin inline of the emitLiteral call.
|
||||||
|
//
|
||||||
|
// d += emitLiteral(dst[d:], src[nextEmit:s])
|
||||||
|
|
||||||
|
MOVL AX, BX
|
||||||
|
SUBL $1, BX
|
||||||
|
|
||||||
|
CMPL BX, $60
|
||||||
|
JLT inlineEmitLiteralOneByte
|
||||||
|
CMPL BX, $256
|
||||||
|
JLT inlineEmitLiteralTwoBytes
|
||||||
|
|
||||||
|
inlineEmitLiteralThreeBytes:
|
||||||
|
MOVB $0xf4, 0(DI)
|
||||||
|
MOVW BX, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
JMP inlineEmitLiteralMemmove
|
||||||
|
|
||||||
|
inlineEmitLiteralTwoBytes:
|
||||||
|
MOVB $0xf0, 0(DI)
|
||||||
|
MOVB BX, 1(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
JMP inlineEmitLiteralMemmove
|
||||||
|
|
||||||
|
inlineEmitLiteralOneByte:
|
||||||
|
SHLB $2, BX
|
||||||
|
MOVB BX, 0(DI)
|
||||||
|
ADDQ $1, DI
|
||||||
|
|
||||||
|
inlineEmitLiteralMemmove:
|
||||||
|
// Spill local variables (registers) onto the stack; call; unspill.
|
||||||
|
//
|
||||||
|
// copy(dst[i:], lit)
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
||||||
|
// DI, R10 and AX as arguments.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ R10, 8(SP)
|
||||||
|
MOVQ AX, 16(SP)
|
||||||
|
ADDQ AX, DI // Finish the "d +=" part of "d += emitLiteral(etc)".
|
||||||
|
MOVQ SI, 72(SP)
|
||||||
|
MOVQ DI, 80(SP)
|
||||||
|
MOVQ R15, 112(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
MOVQ 56(SP), CX
|
||||||
|
MOVQ 64(SP), DX
|
||||||
|
MOVQ 72(SP), SI
|
||||||
|
MOVQ 80(SP), DI
|
||||||
|
MOVQ 88(SP), R9
|
||||||
|
MOVQ 112(SP), R15
|
||||||
|
JMP inner1
|
||||||
|
|
||||||
|
inlineEmitLiteralEnd:
|
||||||
|
// End inline of the emitLiteral call.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
emitLiteralFastPath:
|
||||||
|
// !!! Emit the 1-byte encoding "uint8(len(lit)-1)<<2".
|
||||||
|
MOVB AX, BX
|
||||||
|
SUBB $1, BX
|
||||||
|
SHLB $2, BX
|
||||||
|
MOVB BX, (DI)
|
||||||
|
ADDQ $1, DI
|
||||||
|
|
||||||
|
// !!! Implement the copy from lit to dst as a 16-byte load and store.
|
||||||
|
// (Encode's documentation says that dst and src must not overlap.)
|
||||||
|
//
|
||||||
|
// This always copies 16 bytes, instead of only len(lit) bytes, but that's
|
||||||
|
// OK. Subsequent iterations will fix up the overrun.
|
||||||
|
//
|
||||||
|
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||||
|
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||||
|
// effective on architectures that are fussier about alignment.
|
||||||
|
MOVOU 0(R10), X0
|
||||||
|
MOVOU X0, 0(DI)
|
||||||
|
ADDQ AX, DI
|
||||||
|
|
||||||
|
inner1:
|
||||||
|
// for { etc }
|
||||||
|
|
||||||
|
// base := s
|
||||||
|
MOVQ SI, R12
|
||||||
|
|
||||||
|
// !!! offset := base - candidate
|
||||||
|
MOVQ R12, R11
|
||||||
|
SUBQ R15, R11
|
||||||
|
SUBQ DX, R11
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Begin inline of the extendMatch call.
|
||||||
|
//
|
||||||
|
// s = extendMatch(src, candidate+4, s+4)
|
||||||
|
|
||||||
|
// !!! R14 = &src[len(src)]
|
||||||
|
MOVQ src_len+32(FP), R14
|
||||||
|
ADDQ DX, R14
|
||||||
|
|
||||||
|
// !!! R13 = &src[len(src) - 8]
|
||||||
|
MOVQ R14, R13
|
||||||
|
SUBQ $8, R13
|
||||||
|
|
||||||
|
// !!! R15 = &src[candidate + 4]
|
||||||
|
ADDQ $4, R15
|
||||||
|
ADDQ DX, R15
|
||||||
|
|
||||||
|
// !!! s += 4
|
||||||
|
ADDQ $4, SI
|
||||||
|
|
||||||
|
inlineExtendMatchCmp8:
|
||||||
|
// As long as we are 8 or more bytes before the end of src, we can load and
|
||||||
|
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
||||||
|
CMPQ SI, R13
|
||||||
|
JA inlineExtendMatchCmp1
|
||||||
|
MOVQ (R15), AX
|
||||||
|
MOVQ (SI), BX
|
||||||
|
CMPQ AX, BX
|
||||||
|
JNE inlineExtendMatchBSF
|
||||||
|
ADDQ $8, R15
|
||||||
|
ADDQ $8, SI
|
||||||
|
JMP inlineExtendMatchCmp8
|
||||||
|
|
||||||
|
inlineExtendMatchBSF:
|
||||||
|
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
||||||
|
// the index of the first byte that differs. The BSF instruction finds the
|
||||||
|
// least significant 1 bit, the amd64 architecture is little-endian, and
|
||||||
|
// the shift by 3 converts a bit index to a byte index.
|
||||||
|
XORQ AX, BX
|
||||||
|
BSFQ BX, BX
|
||||||
|
SHRQ $3, BX
|
||||||
|
ADDQ BX, SI
|
||||||
|
JMP inlineExtendMatchEnd
|
||||||
|
|
||||||
|
inlineExtendMatchCmp1:
|
||||||
|
// In src's tail, compare 1 byte at a time.
|
||||||
|
CMPQ SI, R14
|
||||||
|
JAE inlineExtendMatchEnd
|
||||||
|
MOVB (R15), AX
|
||||||
|
MOVB (SI), BX
|
||||||
|
CMPB AX, BX
|
||||||
|
JNE inlineExtendMatchEnd
|
||||||
|
ADDQ $1, R15
|
||||||
|
ADDQ $1, SI
|
||||||
|
JMP inlineExtendMatchCmp1
|
||||||
|
|
||||||
|
inlineExtendMatchEnd:
|
||||||
|
// End inline of the extendMatch call.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Begin inline of the emitCopy call.
|
||||||
|
//
|
||||||
|
// d += emitCopy(dst[d:], base-candidate, s-base)
|
||||||
|
|
||||||
|
// !!! length := s - base
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ R12, AX
|
||||||
|
|
||||||
|
inlineEmitCopyLoop0:
|
||||||
|
// for length >= 68 { etc }
|
||||||
|
CMPL AX, $68
|
||||||
|
JLT inlineEmitCopyStep1
|
||||||
|
|
||||||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xfe, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $64, AX
|
||||||
|
JMP inlineEmitCopyLoop0
|
||||||
|
|
||||||
|
inlineEmitCopyStep1:
|
||||||
|
// if length > 64 { etc }
|
||||||
|
CMPL AX, $64
|
||||||
|
JLE inlineEmitCopyStep2
|
||||||
|
|
||||||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xee, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $60, AX
|
||||||
|
|
||||||
|
inlineEmitCopyStep2:
|
||||||
|
// if length >= 12 || offset >= 2048 { goto inlineEmitCopyStep3 }
|
||||||
|
CMPL AX, $12
|
||||||
|
JGE inlineEmitCopyStep3
|
||||||
|
CMPL R11, $2048
|
||||||
|
JGE inlineEmitCopyStep3
|
||||||
|
|
||||||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||||||
|
MOVB R11, 1(DI)
|
||||||
|
SHRL $8, R11
|
||||||
|
SHLB $5, R11
|
||||||
|
SUBB $4, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB AX, R11
|
||||||
|
ORB $1, R11
|
||||||
|
MOVB R11, 0(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
JMP inlineEmitCopyEnd
|
||||||
|
|
||||||
|
inlineEmitCopyStep3:
|
||||||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||||||
|
SUBL $1, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB $2, AX
|
||||||
|
MOVB AX, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
|
||||||
|
inlineEmitCopyEnd:
|
||||||
|
// End inline of the emitCopy call.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// nextEmit = s
|
||||||
|
MOVQ SI, R10
|
||||||
|
|
||||||
|
// if s >= sLimit { goto emitRemainder }
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
CMPQ AX, R9
|
||||||
|
JAE emitRemainder
|
||||||
|
|
||||||
|
// As per the encode_other.go code:
|
||||||
|
//
|
||||||
|
// We could immediately etc.
|
||||||
|
|
||||||
|
// x := load64(src, s-1)
|
||||||
|
MOVQ -1(SI), R14
|
||||||
|
|
||||||
|
// prevHash := hash(uint32(x>>0), shift)
|
||||||
|
MOVL R14, R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// table[prevHash] = uint16(s-1)
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
SUBQ $1, AX
|
||||||
|
|
||||||
|
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||||
|
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||||
|
BYTE $0x66
|
||||||
|
BYTE $0x42
|
||||||
|
BYTE $0x89
|
||||||
|
BYTE $0x44
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// currHash := hash(uint32(x>>8), shift)
|
||||||
|
SHRQ $8, R14
|
||||||
|
MOVL R14, R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// candidate = int(table[currHash])
|
||||||
|
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
||||||
|
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||||
|
BYTE $0x4e
|
||||||
|
BYTE $0x0f
|
||||||
|
BYTE $0xb7
|
||||||
|
BYTE $0x7c
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// table[currHash] = uint16(s)
|
||||||
|
ADDQ $1, AX
|
||||||
|
|
||||||
|
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||||
|
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||||
|
BYTE $0x66
|
||||||
|
BYTE $0x42
|
||||||
|
BYTE $0x89
|
||||||
|
BYTE $0x44
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// if uint32(x>>8) == load32(src, candidate) { continue }
|
||||||
|
MOVL (DX)(R15*1), BX
|
||||||
|
CMPL R14, BX
|
||||||
|
JEQ inner1
|
||||||
|
|
||||||
|
// nextHash = hash(uint32(x>>16), shift)
|
||||||
|
SHRQ $8, R14
|
||||||
|
MOVL R14, R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// s++
|
||||||
|
ADDQ $1, SI
|
||||||
|
|
||||||
|
// break out of the inner1 for loop, i.e. continue the outer loop.
|
||||||
|
JMP outer
|
||||||
|
|
||||||
|
emitRemainder:
|
||||||
|
// if nextEmit < len(src) { etc }
|
||||||
|
MOVQ src_len+32(FP), AX
|
||||||
|
ADDQ DX, AX
|
||||||
|
CMPQ R10, AX
|
||||||
|
JEQ encodeBlockEnd
|
||||||
|
|
||||||
|
// d += emitLiteral(dst[d:], src[nextEmit:])
|
||||||
|
//
|
||||||
|
// Push args.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ $0, 8(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||||
|
MOVQ $0, 16(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||||
|
MOVQ R10, 24(SP)
|
||||||
|
SUBQ R10, AX
|
||||||
|
MOVQ AX, 32(SP)
|
||||||
|
MOVQ AX, 40(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||||
|
|
||||||
|
// Spill local variables (registers) onto the stack; call; unspill.
|
||||||
|
MOVQ DI, 80(SP)
|
||||||
|
CALL ·emitLiteral(SB)
|
||||||
|
MOVQ 80(SP), DI
|
||||||
|
|
||||||
|
// Finish the "d +=" part of "d += emitLiteral(etc)".
|
||||||
|
ADDQ 48(SP), DI
|
||||||
|
|
||||||
|
encodeBlockEnd:
|
||||||
|
MOVQ dst_base+0(FP), AX
|
||||||
|
SUBQ AX, DI
|
||||||
|
MOVQ DI, d+48(FP)
|
||||||
|
RET
|
||||||
238
vendor/github.com/golang/snappy/encode_other.go
generated
vendored
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
// Copyright 2016 The Snappy-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.
|
||||||
|
|
||||||
|
// +build !amd64 appengine !gc noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
func load32(b []byte, i int) uint32 {
|
||||||
|
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
||||||
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
func load64(b []byte, i int) uint64 {
|
||||||
|
b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
||||||
|
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||||
|
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitLiteral writes a literal chunk and returns the number of bytes written.
|
||||||
|
//
|
||||||
|
// It assumes that:
|
||||||
|
// dst is long enough to hold the encoded bytes
|
||||||
|
// 1 <= len(lit) && len(lit) <= 65536
|
||||||
|
func emitLiteral(dst, lit []byte) int {
|
||||||
|
i, n := 0, uint(len(lit)-1)
|
||||||
|
switch {
|
||||||
|
case n < 60:
|
||||||
|
dst[0] = uint8(n)<<2 | tagLiteral
|
||||||
|
i = 1
|
||||||
|
case n < 1<<8:
|
||||||
|
dst[0] = 60<<2 | tagLiteral
|
||||||
|
dst[1] = uint8(n)
|
||||||
|
i = 2
|
||||||
|
default:
|
||||||
|
dst[0] = 61<<2 | tagLiteral
|
||||||
|
dst[1] = uint8(n)
|
||||||
|
dst[2] = uint8(n >> 8)
|
||||||
|
i = 3
|
||||||
|
}
|
||||||
|
return i + copy(dst[i:], lit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitCopy writes a copy chunk and returns the number of bytes written.
|
||||||
|
//
|
||||||
|
// It assumes that:
|
||||||
|
// dst is long enough to hold the encoded bytes
|
||||||
|
// 1 <= offset && offset <= 65535
|
||||||
|
// 4 <= length && length <= 65535
|
||||||
|
func emitCopy(dst []byte, offset, length int) int {
|
||||||
|
i := 0
|
||||||
|
// The maximum length for a single tagCopy1 or tagCopy2 op is 64 bytes. The
|
||||||
|
// threshold for this loop is a little higher (at 68 = 64 + 4), and the
|
||||||
|
// length emitted down below is is a little lower (at 60 = 64 - 4), because
|
||||||
|
// it's shorter to encode a length 67 copy as a length 60 tagCopy2 followed
|
||||||
|
// by a length 7 tagCopy1 (which encodes as 3+2 bytes) than to encode it as
|
||||||
|
// a length 64 tagCopy2 followed by a length 3 tagCopy2 (which encodes as
|
||||||
|
// 3+3 bytes). The magic 4 in the 64±4 is because the minimum length for a
|
||||||
|
// tagCopy1 op is 4 bytes, which is why a length 3 copy has to be an
|
||||||
|
// encodes-as-3-bytes tagCopy2 instead of an encodes-as-2-bytes tagCopy1.
|
||||||
|
for length >= 68 {
|
||||||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||||||
|
dst[i+0] = 63<<2 | tagCopy2
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
dst[i+2] = uint8(offset >> 8)
|
||||||
|
i += 3
|
||||||
|
length -= 64
|
||||||
|
}
|
||||||
|
if length > 64 {
|
||||||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||||||
|
dst[i+0] = 59<<2 | tagCopy2
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
dst[i+2] = uint8(offset >> 8)
|
||||||
|
i += 3
|
||||||
|
length -= 60
|
||||||
|
}
|
||||||
|
if length >= 12 || offset >= 2048 {
|
||||||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||||||
|
dst[i+0] = uint8(length-1)<<2 | tagCopy2
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
dst[i+2] = uint8(offset >> 8)
|
||||||
|
return i + 3
|
||||||
|
}
|
||||||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||||||
|
dst[i+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
return i + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// extendMatch returns the largest k such that k <= len(src) and that
|
||||||
|
// src[i:i+k-j] and src[j:k] have the same contents.
|
||||||
|
//
|
||||||
|
// It assumes that:
|
||||||
|
// 0 <= i && i < j && j <= len(src)
|
||||||
|
func extendMatch(src []byte, i, j int) int {
|
||||||
|
for ; j < len(src) && src[i] == src[j]; i, j = i+1, j+1 {
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(u, shift uint32) uint32 {
|
||||||
|
return (u * 0x1e35a7bd) >> shift
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It
|
||||||
|
// assumes that the varint-encoded length of the decompressed bytes has already
|
||||||
|
// been written.
|
||||||
|
//
|
||||||
|
// It also assumes that:
|
||||||
|
// len(dst) >= MaxEncodedLen(len(src)) &&
|
||||||
|
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
|
||||||
|
func encodeBlock(dst, src []byte) (d int) {
|
||||||
|
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
||||||
|
// The table element type is uint16, as s < sLimit and sLimit < len(src)
|
||||||
|
// and len(src) <= maxBlockSize and maxBlockSize == 65536.
|
||||||
|
const (
|
||||||
|
maxTableSize = 1 << 14
|
||||||
|
// tableMask is redundant, but helps the compiler eliminate bounds
|
||||||
|
// checks.
|
||||||
|
tableMask = maxTableSize - 1
|
||||||
|
)
|
||||||
|
shift := uint32(32 - 8)
|
||||||
|
for tableSize := 1 << 8; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
||||||
|
shift--
|
||||||
|
}
|
||||||
|
// In Go, all array elements are zero-initialized, so there is no advantage
|
||||||
|
// to a smaller tableSize per se. However, it matches the C++ algorithm,
|
||||||
|
// and in the asm versions of this code, we can get away with zeroing only
|
||||||
|
// the first tableSize elements.
|
||||||
|
var table [maxTableSize]uint16
|
||||||
|
|
||||||
|
// sLimit is when to stop looking for offset/length copies. The inputMargin
|
||||||
|
// lets us use a fast path for emitLiteral in the main loop, while we are
|
||||||
|
// looking for copies.
|
||||||
|
sLimit := len(src) - inputMargin
|
||||||
|
|
||||||
|
// nextEmit is where in src the next emitLiteral should start from.
|
||||||
|
nextEmit := 0
|
||||||
|
|
||||||
|
// The encoded form must start with a literal, as there are no previous
|
||||||
|
// bytes to copy, so we start looking for hash matches at s == 1.
|
||||||
|
s := 1
|
||||||
|
nextHash := hash(load32(src, s), shift)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Copied from the C++ snappy implementation:
|
||||||
|
//
|
||||||
|
// Heuristic match skipping: If 32 bytes are scanned with no matches
|
||||||
|
// found, start looking only at every other byte. If 32 more bytes are
|
||||||
|
// scanned (or skipped), look at every third byte, etc.. When a match
|
||||||
|
// is found, immediately go back to looking at every byte. This is a
|
||||||
|
// small loss (~5% performance, ~0.1% density) for compressible data
|
||||||
|
// due to more bookkeeping, but for non-compressible data (such as
|
||||||
|
// JPEG) it's a huge win since the compressor quickly "realizes" the
|
||||||
|
// data is incompressible and doesn't bother looking for matches
|
||||||
|
// everywhere.
|
||||||
|
//
|
||||||
|
// The "skip" variable keeps track of how many bytes there are since
|
||||||
|
// the last match; dividing it by 32 (ie. right-shifting by five) gives
|
||||||
|
// the number of bytes to move ahead for each iteration.
|
||||||
|
skip := 32
|
||||||
|
|
||||||
|
nextS := s
|
||||||
|
candidate := 0
|
||||||
|
for {
|
||||||
|
s = nextS
|
||||||
|
bytesBetweenHashLookups := skip >> 5
|
||||||
|
nextS = s + bytesBetweenHashLookups
|
||||||
|
skip += bytesBetweenHashLookups
|
||||||
|
if nextS > sLimit {
|
||||||
|
goto emitRemainder
|
||||||
|
}
|
||||||
|
candidate = int(table[nextHash&tableMask])
|
||||||
|
table[nextHash&tableMask] = uint16(s)
|
||||||
|
nextHash = hash(load32(src, nextS), shift)
|
||||||
|
if load32(src, s) == load32(src, candidate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A 4-byte match has been found. We'll later see if more than 4 bytes
|
||||||
|
// match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
|
||||||
|
// them as literal bytes.
|
||||||
|
d += emitLiteral(dst[d:], src[nextEmit:s])
|
||||||
|
|
||||||
|
// Call emitCopy, and then see if another emitCopy could be our next
|
||||||
|
// move. Repeat until we find no match for the input immediately after
|
||||||
|
// what was consumed by the last emitCopy call.
|
||||||
|
//
|
||||||
|
// If we exit this loop normally then we need to call emitLiteral next,
|
||||||
|
// though we don't yet know how big the literal will be. We handle that
|
||||||
|
// by proceeding to the next iteration of the main loop. We also can
|
||||||
|
// exit this loop via goto if we get close to exhausting the input.
|
||||||
|
for {
|
||||||
|
// Invariant: we have a 4-byte match at s, and no need to emit any
|
||||||
|
// literal bytes prior to s.
|
||||||
|
base := s
|
||||||
|
|
||||||
|
// Extend the 4-byte match as long as possible.
|
||||||
|
//
|
||||||
|
// This is an inlined version of:
|
||||||
|
// s = extendMatch(src, candidate+4, s+4)
|
||||||
|
s += 4
|
||||||
|
for i := candidate + 4; s < len(src) && src[i] == src[s]; i, s = i+1, s+1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
d += emitCopy(dst[d:], base-candidate, s-base)
|
||||||
|
nextEmit = s
|
||||||
|
if s >= sLimit {
|
||||||
|
goto emitRemainder
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could immediately start working at s now, but to improve
|
||||||
|
// compression we first update the hash table at s-1 and at s. If
|
||||||
|
// another emitCopy is not our next move, also calculate nextHash
|
||||||
|
// at s+1. At least on GOARCH=amd64, these three hash calculations
|
||||||
|
// are faster as one load64 call (with some shifts) instead of
|
||||||
|
// three load32 calls.
|
||||||
|
x := load64(src, s-1)
|
||||||
|
prevHash := hash(uint32(x>>0), shift)
|
||||||
|
table[prevHash&tableMask] = uint16(s - 1)
|
||||||
|
currHash := hash(uint32(x>>8), shift)
|
||||||
|
candidate = int(table[currHash&tableMask])
|
||||||
|
table[currHash&tableMask] = uint16(s)
|
||||||
|
if uint32(x>>8) != load32(src, candidate) {
|
||||||
|
nextHash = hash(uint32(x>>16), shift)
|
||||||
|
s++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitRemainder:
|
||||||
|
if nextEmit < len(src) {
|
||||||
|
d += emitLiteral(dst[d:], src[nextEmit:])
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
87
vendor/github.com/golang/snappy/snappy.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2011 The Snappy-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 snappy implements the snappy block-based compression format.
|
||||||
|
// It aims for very high speeds and reasonable compression.
|
||||||
|
//
|
||||||
|
// The C++ snappy implementation is at https://github.com/google/snappy
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Each encoded block begins with the varint-encoded length of the decoded data,
|
||||||
|
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
|
||||||
|
first byte of each chunk is broken into its 2 least and 6 most significant bits
|
||||||
|
called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
|
||||||
|
Zero means a literal tag. All other values mean a copy tag.
|
||||||
|
|
||||||
|
For literal tags:
|
||||||
|
- If m < 60, the next 1 + m bytes are literal bytes.
|
||||||
|
- Otherwise, let n be the little-endian unsigned integer denoted by the next
|
||||||
|
m - 59 bytes. The next 1 + n bytes after that are literal bytes.
|
||||||
|
|
||||||
|
For copy tags, length bytes are copied from offset bytes ago, in the style of
|
||||||
|
Lempel-Ziv compression algorithms. In particular:
|
||||||
|
- For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
|
||||||
|
The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
|
||||||
|
of the offset. The next byte is bits 0-7 of the offset.
|
||||||
|
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
|
||||||
|
The length is 1 + m. The offset is the little-endian unsigned integer
|
||||||
|
denoted by the next 2 bytes.
|
||||||
|
- For l == 3, this tag is a legacy format that is no longer issued by most
|
||||||
|
encoders. Nonetheless, the offset ranges in [0, 1<<32) and the length in
|
||||||
|
[1, 65). The length is 1 + m. The offset is the little-endian unsigned
|
||||||
|
integer denoted by the next 4 bytes.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
tagLiteral = 0x00
|
||||||
|
tagCopy1 = 0x01
|
||||||
|
tagCopy2 = 0x02
|
||||||
|
tagCopy4 = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
checksumSize = 4
|
||||||
|
chunkHeaderSize = 4
|
||||||
|
magicChunk = "\xff\x06\x00\x00" + magicBody
|
||||||
|
magicBody = "sNaPpY"
|
||||||
|
|
||||||
|
// maxBlockSize is the maximum size of the input to encodeBlock. It is not
|
||||||
|
// part of the wire format per se, but some parts of the encoder assume
|
||||||
|
// that an offset fits into a uint16.
|
||||||
|
//
|
||||||
|
// Also, for the framing format (Writer type instead of Encode function),
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
||||||
|
// that "the uncompressed data in a chunk must be no longer than 65536
|
||||||
|
// bytes".
|
||||||
|
maxBlockSize = 65536
|
||||||
|
|
||||||
|
// maxEncodedLenOfMaxBlockSize equals MaxEncodedLen(maxBlockSize), but is
|
||||||
|
// hard coded to be a const instead of a variable, so that obufLen can also
|
||||||
|
// be a const. Their equivalence is confirmed by
|
||||||
|
// TestMaxEncodedLenOfMaxBlockSize.
|
||||||
|
maxEncodedLenOfMaxBlockSize = 76490
|
||||||
|
|
||||||
|
obufHeaderLen = len(magicChunk) + checksumSize + chunkHeaderSize
|
||||||
|
obufLen = obufHeaderLen + maxEncodedLenOfMaxBlockSize
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
chunkTypeCompressedData = 0x00
|
||||||
|
chunkTypeUncompressedData = 0x01
|
||||||
|
chunkTypePadding = 0xfe
|
||||||
|
chunkTypeStreamIdentifier = 0xff
|
||||||
|
)
|
||||||
|
|
||||||
|
var crcTable = crc32.MakeTable(crc32.Castagnoli)
|
||||||
|
|
||||||
|
// crc implements the checksum specified in section 3 of
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
func crc(b []byte) uint32 {
|
||||||
|
c := crc32.Update(0, crcTable, b)
|
||||||
|
return uint32(c>>15|c<<17) + 0xa282ead8
|
||||||
|
}
|
||||||
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 HOLDER 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.
|
||||||
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
# errors [](https://travis-ci.org/pkg/errors) [](https://ci.appveyor.com/project/davecheney/errors/branch/master) [](http://godoc.org/github.com/pkg/errors) [](https://goreportcard.com/report/github.com/pkg/errors)
|
||||||
|
|
||||||
|
Package errors provides simple error handling primitives.
|
||||||
|
|
||||||
|
`go get github.com/pkg/errors`
|
||||||
|
|
||||||
|
The traditional error handling idiom in Go is roughly akin to
|
||||||
|
```go
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||||
|
|
||||||
|
## Adding context to an error
|
||||||
|
|
||||||
|
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||||
|
```go
|
||||||
|
_, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "read failed")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Retrieving the cause of an error
|
||||||
|
|
||||||
|
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||||
|
```go
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||||
|
```go
|
||||||
|
switch err := errors.Cause(err).(type) {
|
||||||
|
case *MyError:
|
||||||
|
// handle specifically
|
||||||
|
default:
|
||||||
|
// unknown error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||||
|
|
||||||
|
Before proposing a change, please discuss your change by raising an issue.
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
BSD-2-Clause
|
||||||
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
version: build-{build}.{branch}
|
||||||
|
|
||||||
|
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||||
|
shallow_clone: true # for startup speed
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: C:\gopath
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
|
||||||
|
# http://www.appveyor.com/docs/installed-software
|
||||||
|
install:
|
||||||
|
# some helpful output for debugging builds
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||||
|
# but MSYS2 at C:\msys64 has mingw64
|
||||||
|
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||||
|
- gcc --version
|
||||||
|
- g++ --version
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- go install -v ./...
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- set PATH=C:\gopath\bin;%PATH%
|
||||||
|
- go test -v ./...
|
||||||
|
|
||||||
|
#artifacts:
|
||||||
|
# - path: '%GOPATH%\bin\*.exe'
|
||||||
|
deploy: off
|
||||||
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Executable file
@ -0,0 +1,269 @@
|
|||||||
|
// Package errors provides simple error handling primitives.
|
||||||
|
//
|
||||||
|
// The traditional error handling idiom in Go is roughly akin to
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// which applied recursively up the call stack results in error reports
|
||||||
|
// without context or debugging information. The errors package allows
|
||||||
|
// programmers to add context to the failure path in their code in a way
|
||||||
|
// that does not destroy the original value of the error.
|
||||||
|
//
|
||||||
|
// Adding context to an error
|
||||||
|
//
|
||||||
|
// The errors.Wrap function returns a new error that adds context to the
|
||||||
|
// original error by recording a stack trace at the point Wrap is called,
|
||||||
|
// and the supplied message. For example
|
||||||
|
//
|
||||||
|
// _, err := ioutil.ReadAll(r)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "read failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||||
|
// functions destructure errors.Wrap into its component operations of annotating
|
||||||
|
// an error with a stack trace and an a message, respectively.
|
||||||
|
//
|
||||||
|
// Retrieving the cause of an error
|
||||||
|
//
|
||||||
|
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||||
|
// preceding error. Depending on the nature of the error it may be necessary
|
||||||
|
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||||
|
// for inspection. Any error value which implements this interface
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||||
|
// the topmost error which does not implement causer, which is assumed to be
|
||||||
|
// the original cause. For example:
|
||||||
|
//
|
||||||
|
// switch err := errors.Cause(err).(type) {
|
||||||
|
// case *MyError:
|
||||||
|
// // handle specifically
|
||||||
|
// default:
|
||||||
|
// // unknown error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// causer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// Formatted printing of errors
|
||||||
|
//
|
||||||
|
// All error values returned from this package implement fmt.Formatter and can
|
||||||
|
// be formatted by the fmt package. The following verbs are supported
|
||||||
|
//
|
||||||
|
// %s print the error. If the error has a Cause it will be
|
||||||
|
// printed recursively
|
||||||
|
// %v see %s
|
||||||
|
// %+v extended format. Each Frame of the error's StackTrace will
|
||||||
|
// be printed in detail.
|
||||||
|
//
|
||||||
|
// Retrieving the stack trace of an error or wrapper
|
||||||
|
//
|
||||||
|
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||||
|
// invoked. This information can be retrieved with the following interface.
|
||||||
|
//
|
||||||
|
// type stackTracer interface {
|
||||||
|
// StackTrace() errors.StackTrace
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Where errors.StackTrace is defined as
|
||||||
|
//
|
||||||
|
// type StackTrace []Frame
|
||||||
|
//
|
||||||
|
// The Frame type represents a call site in the stack trace. Frame supports
|
||||||
|
// the fmt.Formatter interface that can be used for printing information about
|
||||||
|
// the stack trace of this error. For example:
|
||||||
|
//
|
||||||
|
// if err, ok := err.(stackTracer); ok {
|
||||||
|
// for _, f := range err.StackTrace() {
|
||||||
|
// fmt.Printf("%+s:%d", f)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// stackTracer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// See the documentation for Frame.Format for more details.
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns an error with the supplied message.
|
||||||
|
// New also records the stack trace at the point it was called.
|
||||||
|
func New(message string) error {
|
||||||
|
return &fundamental{
|
||||||
|
msg: message,
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf formats according to a format specifier and returns the string
|
||||||
|
// as a value that satisfies error.
|
||||||
|
// Errorf also records the stack trace at the point it was called.
|
||||||
|
func Errorf(format string, args ...interface{}) error {
|
||||||
|
return &fundamental{
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fundamental is an error that has a message and a stack, but no caller.
|
||||||
|
type fundamental struct {
|
||||||
|
msg string
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fundamental) Error() string { return f.msg }
|
||||||
|
|
||||||
|
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
f.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", f.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||||
|
// If err is nil, WithStack returns nil.
|
||||||
|
func WithStack(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withStack struct {
|
||||||
|
error
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withStack) Cause() error { return w.error }
|
||||||
|
|
||||||
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v", w.Cause())
|
||||||
|
w.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrap is called, and the supplied message.
|
||||||
|
// If err is nil, Wrap returns nil.
|
||||||
|
func Wrap(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrapf is call, and the format specifier.
|
||||||
|
// If err is nil, Wrapf returns nil.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessage annotates err with a new message.
|
||||||
|
// If err is nil, WithMessage returns nil.
|
||||||
|
func WithMessage(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withMessage struct {
|
||||||
|
cause error
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||||
|
func (w *withMessage) Cause() error { return w.cause }
|
||||||
|
|
||||||
|
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||||
|
io.WriteString(s, w.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's', 'q':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the underlying cause of the error, if possible.
|
||||||
|
// An error value has a cause if it implements the following
|
||||||
|
// interface:
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If the error does not implement Cause, the original error will
|
||||||
|
// be returned. If the error is nil, nil will be returned without further
|
||||||
|
// investigation.
|
||||||
|
func Cause(err error) error {
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
|
||||||
|
for err != nil {
|
||||||
|
cause, ok := err.(causer)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = cause.Cause()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
186
vendor/github.com/pkg/errors/stack.go
generated
vendored
Executable file
@ -0,0 +1,186 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame represents a program counter inside a stack frame.
|
||||||
|
type Frame uintptr
|
||||||
|
|
||||||
|
// pc returns the program counter for this frame;
|
||||||
|
// multiple frames may have the same PC value.
|
||||||
|
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||||
|
|
||||||
|
// file returns the full path to the file that contains the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) file() string {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
file, _ := fn.FileLine(f.pc())
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// line returns the line number of source code of the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) line() int {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
_, line := fn.FileLine(f.pc())
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the frame according to the fmt.Formatter interface.
|
||||||
|
//
|
||||||
|
// %s source file
|
||||||
|
// %d source line
|
||||||
|
// %n function name
|
||||||
|
// %v equivalent to %s:%d
|
||||||
|
//
|
||||||
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||||
|
//
|
||||||
|
// %+s path of source file relative to the compile time GOPATH
|
||||||
|
// %+v equivalent to %+s:%d
|
||||||
|
func (f Frame) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 's':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
pc := f.pc()
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
io.WriteString(s, "unknown")
|
||||||
|
} else {
|
||||||
|
file, _ := fn.FileLine(pc)
|
||||||
|
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
io.WriteString(s, path.Base(f.file()))
|
||||||
|
}
|
||||||
|
case 'd':
|
||||||
|
fmt.Fprintf(s, "%d", f.line())
|
||||||
|
case 'n':
|
||||||
|
name := runtime.FuncForPC(f.pc()).Name()
|
||||||
|
io.WriteString(s, funcname(name))
|
||||||
|
case 'v':
|
||||||
|
f.Format(s, 's')
|
||||||
|
io.WriteString(s, ":")
|
||||||
|
f.Format(s, 'd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
|
type StackTrace []Frame
|
||||||
|
|
||||||
|
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||||
|
//
|
||||||
|
// %s lists source files for each Frame in the stack
|
||||||
|
// %v lists the source file and line number for each Frame in the stack
|
||||||
|
//
|
||||||
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||||
|
//
|
||||||
|
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||||
|
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
for _, f := range st {
|
||||||
|
fmt.Fprintf(s, "\n%+v", f)
|
||||||
|
}
|
||||||
|
case s.Flag('#'):
|
||||||
|
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(s, "%v", []Frame(st))
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
fmt.Fprintf(s, "%s", []Frame(st))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
type stack []uintptr
|
||||||
|
|
||||||
|
func (s *stack) Format(st fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case st.Flag('+'):
|
||||||
|
for _, pc := range *s {
|
||||||
|
f := Frame(pc)
|
||||||
|
fmt.Fprintf(st, "\n%+v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stack) StackTrace() StackTrace {
|
||||||
|
f := make([]Frame, len(*s))
|
||||||
|
for i := 0; i < len(f); i++ {
|
||||||
|
f[i] = Frame((*s)[i])
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func callers() *stack {
|
||||||
|
const depth = 32
|
||||||
|
var pcs [depth]uintptr
|
||||||
|
n := runtime.Callers(3, pcs[:])
|
||||||
|
var st stack = pcs[0:n]
|
||||||
|
return &st
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||||
|
func funcname(name string) string {
|
||||||
|
i := strings.LastIndex(name, "/")
|
||||||
|
name = name[i+1:]
|
||||||
|
i = strings.Index(name, ".")
|
||||||
|
return name[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimGOPATH(name, file string) string {
|
||||||
|
// Here we want to get the source file path relative to the compile time
|
||||||
|
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||||
|
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||||
|
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||||
|
// the import path, which does not include the GOPATH. Thus we can trim
|
||||||
|
// segments from the beginning of the file path until the number of path
|
||||||
|
// separators remaining is one more than the number of path separators in
|
||||||
|
// the function name. For example, given:
|
||||||
|
//
|
||||||
|
// GOPATH /home/user
|
||||||
|
// file /home/user/src/pkg/sub/file.go
|
||||||
|
// fn.Name() pkg/sub.Type.Method
|
||||||
|
//
|
||||||
|
// We want to produce:
|
||||||
|
//
|
||||||
|
// pkg/sub/file.go
|
||||||
|
//
|
||||||
|
// From this we can easily see that fn.Name() has one less path separator
|
||||||
|
// than our desired output. We count separators from the end of the file
|
||||||
|
// path until it finds two more than in the function name and then move
|
||||||
|
// one character forward to preserve the initial path segment without a
|
||||||
|
// leading separator.
|
||||||
|
const sep = "/"
|
||||||
|
goal := strings.Count(name, sep) + 2
|
||||||
|
i := len(file)
|
||||||
|
for n := 0; n < goal; n++ {
|
||||||
|
i = strings.LastIndex(file[:i], sep)
|
||||||
|
if i == -1 {
|
||||||
|
// not enough separators found, set i so that the slice expression
|
||||||
|
// below leaves file unmodified
|
||||||
|
i = -len(sep)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get back to 0 or trim the leading separator
|
||||||
|
file = file[i+len(sep):]
|
||||||
|
return file
|
||||||
|
}
|
||||||
27
vendor/github.com/templexxx/cpufeat/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 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.
|
||||||
32
vendor/github.com/templexxx/cpufeat/cpu.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2017 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 cpu implements processor feature detection
|
||||||
|
// used by the Go standard libary.
|
||||||
|
package cpufeat
|
||||||
|
|
||||||
|
var X86 x86
|
||||||
|
|
||||||
|
// The booleans in x86 contain the correspondingly named cpuid feature bit.
|
||||||
|
// HasAVX and HasAVX2 are only set if the OS does support XMM and YMM registers
|
||||||
|
// in addition to the cpuid feature bit being set.
|
||||||
|
// The struct is padded to avoid false sharing.
|
||||||
|
type x86 struct {
|
||||||
|
_ [CacheLineSize]byte
|
||||||
|
HasAES bool
|
||||||
|
HasAVX bool
|
||||||
|
HasAVX2 bool
|
||||||
|
HasBMI1 bool
|
||||||
|
HasBMI2 bool
|
||||||
|
HasERMS bool
|
||||||
|
HasOSXSAVE bool
|
||||||
|
HasPCLMULQDQ bool
|
||||||
|
HasPOPCNT bool
|
||||||
|
HasSSE2 bool
|
||||||
|
HasSSE3 bool
|
||||||
|
HasSSSE3 bool
|
||||||
|
HasSSE41 bool
|
||||||
|
HasSSE42 bool
|
||||||
|
_ [CacheLineSize]byte
|
||||||
|
}
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_arm.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 32
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_arm64.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 32
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_mips.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 32
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_mips64.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 32
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_mips64le.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 32
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_mipsle.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 32
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_ppc64.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 128
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 128
|
||||||
7
vendor/github.com/templexxx/cpufeat/cpu_s390x.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017 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 cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 256
|
||||||
59
vendor/github.com/templexxx/cpufeat/cpu_x86.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
// +build 386 amd64 amd64p32
|
||||||
|
|
||||||
|
package cpufeat
|
||||||
|
|
||||||
|
const CacheLineSize = 64
|
||||||
|
|
||||||
|
// cpuid is implemented in cpu_x86.s.
|
||||||
|
func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
|
||||||
|
// xgetbv with ecx = 0 is implemented in cpu_x86.s.
|
||||||
|
func xgetbv() (eax, edx uint32)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
maxId, _, _, _ := cpuid(0, 0)
|
||||||
|
|
||||||
|
if maxId < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ecx1, edx1 := cpuid(1, 0)
|
||||||
|
X86.HasSSE2 = isSet(26, edx1)
|
||||||
|
|
||||||
|
X86.HasSSE3 = isSet(0, ecx1)
|
||||||
|
X86.HasPCLMULQDQ = isSet(1, ecx1)
|
||||||
|
X86.HasSSSE3 = isSet(9, ecx1)
|
||||||
|
X86.HasSSE41 = isSet(19, ecx1)
|
||||||
|
X86.HasSSE42 = isSet(20, ecx1)
|
||||||
|
X86.HasPOPCNT = isSet(23, ecx1)
|
||||||
|
X86.HasAES = isSet(25, ecx1)
|
||||||
|
X86.HasOSXSAVE = isSet(27, ecx1)
|
||||||
|
|
||||||
|
osSupportsAVX := false
|
||||||
|
// For XGETBV, OSXSAVE bit is required and sufficient.
|
||||||
|
if X86.HasOSXSAVE {
|
||||||
|
eax, _ := xgetbv()
|
||||||
|
// Check if XMM and YMM registers have OS support.
|
||||||
|
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
|
||||||
|
}
|
||||||
|
|
||||||
|
X86.HasAVX = isSet(28, ecx1) && osSupportsAVX
|
||||||
|
|
||||||
|
if maxId < 7 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ebx7, _, _ := cpuid(7, 0)
|
||||||
|
X86.HasBMI1 = isSet(3, ebx7)
|
||||||
|
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
|
||||||
|
X86.HasBMI2 = isSet(8, ebx7)
|
||||||
|
X86.HasERMS = isSet(9, ebx7)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSet(bitpos uint, value uint32) bool {
|
||||||
|
return value&(1<<bitpos) != 0
|
||||||
|
}
|
||||||