diff --git a/go.mod b/go.mod index c644e140..5b662762 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/go-gost/x -go 1.23.0 +go 1.23.1 -toolchain go1.24.4 +toolchain go1.24.5 require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gin-contrib/cors v1.7.2 - github.com/gin-gonic/gin v1.10.0 + github.com/gin-gonic/gin v1.10.1 github.com/go-gost/core v0.3.2 github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.4.2 @@ -30,29 +30,32 @@ require ( github.com/quic-go/webtransport-go v0.9.0 github.com/rs/xid v1.3.0 github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 - github.com/sirupsen/logrus v1.8.1 + github.com/sirupsen/logrus v1.9.3 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - github.com/vishvananda/netlink v1.1.0 + github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 github.com/vishvananda/netns v0.0.4 github.com/vulcand/predicate v1.2.0 + github.com/xjasonlyu/tun2socks/v2 v2.6.0 github.com/xtaci/kcp-go/v5 v5.6.5 github.com/xtaci/smux v1.5.31 github.com/xtaci/tcpraw v1.2.25 github.com/yl2chen/cidranger v1.0.2 github.com/zalando/go-keyring v0.2.4 - golang.org/x/crypto v0.39.0 + go.uber.org/atomic v1.11.0 + golang.org/x/crypto v0.40.0 golang.org/x/exp v0.0.0-20241210194714-1829a127f884 - golang.org/x/net v0.40.0 - golang.org/x/sys v0.33.0 - golang.org/x/text v0.26.0 - golang.org/x/time v0.5.0 - golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 + golang.org/x/net v0.42.0 + golang.org/x/sys v0.34.0 + golang.org/x/text v0.27.0 + golang.org/x/time v0.11.0 + golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 + gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 ) require ( @@ -75,6 +78,7 @@ require ( github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -116,10 +120,12 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/term v0.33.0 // indirect + golang.org/x/tools v0.34.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) + +replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go/codec v1.2.12 diff --git a/go.sum b/go.sum index 7d15b386..fd0bc709 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQ github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-gost/core v0.3.2 h1:AjmIYWVa5fc172zIupT3e1pdnKx76LxppGiw00uRsSA= github.com/go-gost/core v0.3.2/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= @@ -90,8 +90,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -193,8 +193,8 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 h1:XdDrN8rtxdgW3TLn7pAuobI9PhPMbf6Geu9nvFzXn2E= github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740/go.mod h1:Oqfn/ykzqjeX00+7IuPyR7wGYgOzld0Tni6djgElacI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -235,13 +235,15 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= +github.com/xjasonlyu/tun2socks/v2 v2.6.0 h1:gI9saJT3XgH4e6v9jBuHRLwK7l3aN9YFWec/SsDTDx4= +github.com/xjasonlyu/tun2socks/v2 v2.6.0/go.mod h1:35AwqxIxnMkfBfT0UJ1Lku7PZm2ZiZJ8sxHyp0gt1yw= github.com/xtaci/kcp-go/v5 v5.6.5 h1:oxGZNobj3OddrLzwdJYnR/waNgwrL98u02u0DWNHE3k= github.com/xtaci/kcp-go/v5 v5.6.5/go.mod h1:Qy3Zf2tWTdFdEs0E8JvhrX+39r5UDZoYac8anvud7/Q= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= @@ -255,6 +257,8 @@ github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -269,8 +273,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= @@ -294,45 +298,47 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -341,15 +347,15 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= +golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -384,8 +390,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= +gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 h1:0DxLu8hxI1OGp1qVRPqNd+2k1a7hMNUNqbZG0IrtKlM= +gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/handler/vtun/endpoint.go b/handler/vtun/endpoint.go new file mode 100644 index 00000000..f39bfb2d --- /dev/null +++ b/handler/vtun/endpoint.go @@ -0,0 +1,161 @@ +// Package iobased provides the implementation of io.ReadWriter +// based data-link layer endpoints. +package tun + +import ( + "context" + "errors" + "io" + "sync" + + "github.com/go-gost/core/logger" + "gvisor.dev/gvisor/pkg/buffer" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +const ( + // Queue length for outbound packet, arriving for read. Overflow + // causes packet drops. + defaultOutQueueLen = 1 << 10 +) + +// Endpoint implements the interface of stack.LinkEndpoint from io.ReadWriter. +type Endpoint struct { + *channel.Endpoint + + // rw is the io.ReadWriter for reading and writing packets. + rw io.ReadWriter + + // mtu (maximum transmission unit) is the maximum size of a packet. + mtu uint32 + + // offset can be useful when perform TUN device I/O with TUN_PI enabled. + offset int + + // once is used to perform the init action once when attaching. + once sync.Once + + // wg keeps track of running goroutines. + wg sync.WaitGroup + + log logger.Logger +} + +// New returns stack.LinkEndpoint(.*Endpoint) and error. +func newEndpoint(rw io.ReadWriter, mtu uint32, offset int, log logger.Logger) (*Endpoint, error) { + if mtu == 0 { + return nil, errors.New("MTU size is zero") + } + + if rw == nil { + return nil, errors.New("RW interface is nil") + } + + if offset < 0 { + return nil, errors.New("offset must be non-negative") + } + + return &Endpoint{ + Endpoint: channel.New(defaultOutQueueLen, mtu, ""), + rw: rw, + mtu: mtu, + offset: offset, + log: log, + }, nil +} + +// Attach launches the goroutine that reads packets from io.Reader and +// dispatches them via the provided dispatcher. +func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) { + e.Endpoint.Attach(dispatcher) + e.once.Do(func() { + ctx, cancel := context.WithCancel(context.Background()) + e.wg.Add(2) + go func() { + e.outboundLoop(ctx) + e.wg.Done() + }() + go func() { + e.dispatchLoop(cancel) + e.wg.Done() + }() + }) +} + +func (e *Endpoint) Wait() { + e.wg.Wait() +} + +// dispatchLoop dispatches packets to upper layer. +func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) { + // Call cancel() to ensure (*Endpoint).outboundLoop(context.Context) exits + // gracefully after (*Endpoint).dispatchLoop(context.CancelFunc) returns. + defer cancel() + + offset, mtu := e.offset, int(e.mtu) + + for { + data := make([]byte, offset+mtu) + + n, err := e.rw.Read(data) + if err != nil { + e.log.Error(err) + break + } + + e.log.Debugf("read tun: (%d) % x", n, data[:n]) + + if n == 0 || n > mtu { + continue + } + + if !e.IsAttached() { + continue /* unattached, drop packet */ + } + + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(data[offset : offset+n]), + }) + + switch header.IPVersion(data[offset:]) { + case header.IPv4Version: + e.InjectInbound(header.IPv4ProtocolNumber, pkt) + case header.IPv6Version: + e.InjectInbound(header.IPv6ProtocolNumber, pkt) + } + pkt.DecRef() + } +} + +// outboundLoop reads outbound packets from channel, and then it calls +// writePacket to send those packets back to lower layer. +func (e *Endpoint) outboundLoop(ctx context.Context) { + for { + pkt := e.ReadContext(ctx) + if pkt == nil { + break + } + e.writePacket(pkt) + } +} + +// writePacket writes outbound packets to the io.Writer. +func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error { + defer pkt.DecRef() + + buf := pkt.ToBuffer() + defer buf.Release() + if e.offset != 0 { + v := buffer.NewViewWithData(make([]byte, e.offset)) + _ = buf.Prepend(v) + } + + if _, err := e.rw.Write(buf.Flatten()); err != nil { + return &tcpip.ErrInvalidEndpointState{} + } + + return nil +} diff --git a/handler/vtun/handler.go b/handler/vtun/handler.go new file mode 100644 index 00000000..e3fb51d9 --- /dev/null +++ b/handler/vtun/handler.go @@ -0,0 +1,84 @@ +package tun + +import ( + "context" + "net" + "time" + + "github.com/go-gost/core/handler" + md "github.com/go-gost/core/metadata" + ctxvalue "github.com/go-gost/x/ctx" + "github.com/go-gost/x/registry" + "github.com/xjasonlyu/tun2socks/v2/core" +) + +func init() { + registry.HandlerRegistry().Register("vtun", NewHandler) +} + +type tunHandler struct { + options handler.Options +} + +func NewHandler(opts ...handler.Option) handler.Handler { + options := handler.Options{} + for _, opt := range opts { + opt(&options) + } + + return &tunHandler{ + options: options, + } +} + +func (h *tunHandler) Init(md md.Metadata) (err error) { + if err = h.parseMetadata(md); err != nil { + return + } + + return +} + +func (h *tunHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { + defer conn.Close() + + log := h.options.Logger + + start := time.Now() + log = log.WithFields(map[string]any{ + "remote": conn.RemoteAddr().String(), + "local": conn.LocalAddr().String(), + "sid": ctxvalue.SidFromContext(ctx), + }) + + log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) + defer func() { + log.WithFields(map[string]any{ + "duration": time.Since(start), + }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) + }() + + ep, err := newEndpoint(conn, 1420, 0, log) + if err != nil { + return err + } + + th := newTransportHandler(log) + th.ProcessAsync() + defer th.Close() + + stack, err := core.CreateStack(&core.Config{ + LinkEndpoint: ep, + TransportHandler: th, + }) + if err != nil { + return err + } + defer stack.Close() + + log.Debugf("is attached: %v", ep.IsAttached()) + + <-ctx.Done() + + return nil +} diff --git a/handler/vtun/metadata.go b/handler/vtun/metadata.go new file mode 100644 index 00000000..98ad6b42 --- /dev/null +++ b/handler/vtun/metadata.go @@ -0,0 +1,12 @@ +package tun + +import ( + mdata "github.com/go-gost/core/metadata" +) + +type metadata struct { +} + +func (h *tunHandler) parseMetadata(md mdata.Metadata) (err error) { + return +} diff --git a/handler/vtun/stack.go b/handler/vtun/stack.go new file mode 100644 index 00000000..66a7fe39 --- /dev/null +++ b/handler/vtun/stack.go @@ -0,0 +1,105 @@ +package tun + +import ( + "context" + "net/netip" + "sync" + "time" + + "github.com/go-gost/core/logger" + "github.com/xjasonlyu/tun2socks/v2/core/adapter" + "go.uber.org/atomic" +) + +const ( + // tcpConnectTimeout is the default timeout for TCP handshakes. + tcpConnectTimeout = 5 * time.Second + // tcpWaitTimeout implements a TCP half-close timeout. + tcpWaitTimeout = 60 * time.Second + // udpSessionTimeout is the default timeout for UDP sessions. + udpSessionTimeout = 60 * time.Second +) + +var _ adapter.TransportHandler = (*transportHandler)(nil) + +type transportHandler struct { + // Unbuffered TCP/UDP queues. + tcpQueue chan adapter.TCPConn + udpQueue chan adapter.UDPConn + + // UDP session timeout. + udpTimeout *atomic.Duration + + procOnce sync.Once + procCancel context.CancelFunc + + log logger.Logger +} + +func newTransportHandler(log logger.Logger) *transportHandler { + return &transportHandler{ + tcpQueue: make(chan adapter.TCPConn), + udpQueue: make(chan adapter.UDPConn), + udpTimeout: atomic.NewDuration(udpSessionTimeout), + procCancel: func() { /* nop */ }, + log: log, + } +} + +func (t *transportHandler) HandleTCP(conn adapter.TCPConn) { + t.tcpQueue <- conn +} + +func (t *transportHandler) HandleUDP(conn adapter.UDPConn) { + t.udpQueue <- conn +} + +func (t *transportHandler) process(ctx context.Context) { + for { + select { + case conn := <-t.tcpQueue: + go t.handleTCPConn(conn) + case conn := <-t.udpQueue: + go t.handleUDPConn(conn) + case <-ctx.Done(): + return + } + } +} + +// ProcessAsync can be safely called multiple times, but will only be effective once. +func (t *transportHandler) ProcessAsync() { + t.procOnce.Do(func() { + ctx, cancel := context.WithCancel(context.Background()) + t.procCancel = cancel + go t.process(ctx) + }) +} + +// Close closes the Tunnel and releases its resources. +func (t *transportHandler) Close() { + t.procCancel() +} + +func (t *transportHandler) SetUDPTimeout(timeout time.Duration) { + t.udpTimeout.Store(timeout) +} + +func (t *transportHandler) handleTCPConn(originConn adapter.TCPConn) { + defer originConn.Close() + + id := originConn.ID() + + srcIP, _ := netip.AddrFromSlice(id.RemoteAddress.AsSlice()) + dstIP, _ := netip.AddrFromSlice(id.LocalAddress.AsSlice()) + + raddr := netip.AddrPortFrom(srcIP, id.RemotePort) + laddr := netip.AddrPortFrom(dstIP, id.LocalPort) + + t.log.Debugf("[TCP] %s <-> %s", raddr.String(), laddr.String()) +} + +// TODO: Port Restricted NAT support. +func (t *transportHandler) handleUDPConn(uc adapter.UDPConn) { + defer uc.Close() +} diff --git a/listener/tun/listener.go b/listener/tun/listener.go index 5524d14f..ecf169e3 100644 --- a/listener/tun/listener.go +++ b/listener/tun/listener.go @@ -27,7 +27,7 @@ type tunListener struct { addr net.Addr cqueue chan net.Conn closed chan struct{} - logger logger.Logger + log logger.Logger md metadata options listener.Options routes []*router.Route @@ -39,7 +39,7 @@ func NewListener(opts ...listener.Option) listener.Listener { opt(&options) } return &tunListener{ - logger: options.Logger, + log: options.Logger, options: options, } } @@ -83,7 +83,7 @@ func (l *tunListener) listenLoop() { } addrs, _ := itf.Addrs() - l.logger.Infof("name: %s, net: %s, mtu: %d, addrs: %s", + l.log.Infof("name: %s, net: %s, mtu: %d, addrs: %s", itf.Name, ip, itf.MTU, addrs) var c net.Conn @@ -112,7 +112,7 @@ func (l *tunListener) listenLoop() { return nil }() if err != nil { - l.logger.Error(err) + l.log.Error(err) cancel() } diff --git a/listener/tun/tun.go b/listener/tun/tun.go index 975f83d6..be769338 100644 --- a/listener/tun/tun.go +++ b/listener/tun/tun.go @@ -7,53 +7,51 @@ import ( "golang.zx2c4.com/wireguard/tun" ) -const ( - tunOffsetBytes = 16 - MaxMessageSize = math.MaxUint16 -) - type tunDevice struct { - dev tun.Device - elems [][MaxMessageSize]byte - rbufs [][]byte - wbuf [tunOffsetBytes + MaxMessageSize]byte + dev tun.Device + packets int + sizes []int + rbufs [][]byte + wbufs [][]byte + wbuf []byte } func (d *tunDevice) Read(p []byte) (n int, err error) { - if len(d.rbufs) > 0 { - n = copy(p, d.rbufs[0]) - d.rbufs = d.rbufs[1:] - return - } + if d.packets > 0 { + for i, size := range d.sizes { + if size > 0 { + n = copy(p, d.rbufs[i][:size]) - sizes := make([]int, len(d.elems)) - bufs := make([][]byte, len(d.elems)) - for i := range d.elems { - bufs[i] = d.elems[i][:] - } - - nn, err := d.dev.Read(bufs, sizes, 0) - if err != nil { - return - } - - for i := 0; i < nn; i++ { - if sizes[i] <= 0 { - continue + d.sizes[i] = 0 + d.packets-- + return + } } - d.rbufs = append(d.rbufs, bufs[i][:sizes[i]]) } - n = copy(p, d.rbufs[0]) - d.rbufs = d.rbufs[1:] + d.rbufs[0] = p + packets, err := d.dev.Read(d.rbufs, d.sizes, readOffset) + if err != nil && err != tun.ErrTooManySegments { + return + } + n = d.sizes[0] + + d.sizes[0] = 0 + d.packets = packets - 1 return } func (d *tunDevice) Write(p []byte) (n int, err error) { - buf := d.wbuf[:] - n = copy(buf[tunOffsetBytes:], p) - _, err = d.dev.Write([][]byte{buf[:tunOffsetBytes+n]}, tunOffsetBytes) + if writeOffset > 0 { + copy(d.wbuf[writeOffset:], p) + d.wbufs[0] = d.wbuf[:writeOffset+len(p)] + } else { + d.wbufs[0] = p + } + + _, err = d.dev.Write(d.wbufs, writeOffset) + n = len(p) return } @@ -67,9 +65,19 @@ func (l *tunListener) createTunDevice() (dev io.ReadWriteCloser, name string, er return } + batchSize := 16 + + rbufs := make([][]byte, batchSize) + for i := 1; i < len(rbufs); i++ { + rbufs[i] = make([]byte, math.MaxUint16) + } + dev = &tunDevice{ dev: ifce, - elems: make([][MaxMessageSize]byte, ifce.BatchSize()), + sizes: make([]int, batchSize), + rbufs: rbufs, + wbufs: make([][]byte, 1), + wbuf: make([]byte, math.MaxUint16+writeOffset), } name, err = ifce.Name() diff --git a/listener/tun/tun_darwin.go b/listener/tun/tun_darwin.go index d5178981..96db894b 100644 --- a/listener/tun/tun_darwin.go +++ b/listener/tun/tun_darwin.go @@ -10,6 +10,8 @@ import ( const ( defaultTunName = "utun" + readOffset = 4 + writeOffset = 4 ) func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) { diff --git a/listener/tun/tun_linux.go b/listener/tun/tun_linux.go index 2ef7d0cd..6302c26e 100644 --- a/listener/tun/tun_linux.go +++ b/listener/tun/tun_linux.go @@ -10,6 +10,11 @@ import ( "github.com/vishvananda/netlink" ) +const ( + readOffset = 0 + writeOffset = 16 +) + func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.IP, err error) { dev, name, err = l.createTunDevice() if err != nil { @@ -30,7 +35,7 @@ func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.I if err = netlink.AddrAdd(link, &netlink.Addr{ IPNet: &net, }); err != nil { - l.logger.Error(err) + l.log.Error(err) continue } } @@ -52,7 +57,7 @@ func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.I } if len(dnsServers) > 0 { cmd := fmt.Sprintf("resolvectl dns %s %s", name, strings.Join(dnsServers, " ")) - l.logger.Debug(cmd) + l.log.Debug(cmd) args := strings.Split(cmd, " ") if er := exec.Command(args[0], args[1:]...).Run(); er != nil { diff --git a/listener/tun/tun_unix.go b/listener/tun/tun_unix.go index 9f9872e9..087bb725 100644 --- a/listener/tun/tun_unix.go +++ b/listener/tun/tun_unix.go @@ -12,6 +12,8 @@ import ( const ( defaultTunName = "tun0" + readOffset = 4 + writeOffset = 4 ) func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) { diff --git a/listener/tun/tun_windows.go b/listener/tun/tun_windows.go index 2d8015eb..0562f83f 100644 --- a/listener/tun/tun_windows.go +++ b/listener/tun/tun_windows.go @@ -10,6 +10,8 @@ import ( const ( defaultTunName = "wintun" + readOffset = 0 + writeOffset = 0 ) func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) { diff --git a/listener/vtun/conn.go b/listener/vtun/conn.go new file mode 100644 index 00000000..43a322c1 --- /dev/null +++ b/listener/vtun/conn.go @@ -0,0 +1,51 @@ +package tun + +import ( + "context" + "errors" + "io" + "net" + "time" +) + +type conn struct { + ifce io.ReadWriteCloser + laddr net.Addr + raddr net.Addr + cancel context.CancelFunc +} + +func (c *conn) Read(b []byte) (n int, err error) { + return c.ifce.Read(b) +} + +func (c *conn) Write(b []byte) (n int, err error) { + return c.ifce.Write(b) +} + +func (c *conn) LocalAddr() net.Addr { + return c.laddr +} + +func (c *conn) RemoteAddr() net.Addr { + return c.raddr +} + +func (c *conn) SetDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *conn) SetReadDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *conn) SetWriteDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (c *conn) Close() (err error) { + if c.cancel != nil { + c.cancel() + } + return c.ifce.Close() +} diff --git a/listener/vtun/listener.go b/listener/vtun/listener.go new file mode 100644 index 00000000..5f0e8478 --- /dev/null +++ b/listener/vtun/listener.go @@ -0,0 +1,151 @@ +package tun + +import ( + "context" + "net" + "time" + + "github.com/go-gost/core/limiter" + "github.com/go-gost/core/listener" + "github.com/go-gost/core/logger" + mdata "github.com/go-gost/core/metadata" + "github.com/go-gost/core/router" + traffic_limiter "github.com/go-gost/x/limiter/traffic" + limiter_wrapper "github.com/go-gost/x/limiter/traffic/wrapper" + metrics "github.com/go-gost/x/metrics/wrapper" + stats "github.com/go-gost/x/observer/stats/wrapper" + "github.com/go-gost/x/registry" +) + +func init() { + registry.ListenerRegistry().Register("vtun", NewListener) +} + +type tunListener struct { + addr net.Addr + cqueue chan net.Conn + closed chan struct{} + log logger.Logger + md metadata + options listener.Options + routes []*router.Route +} + +func NewListener(opts ...listener.Option) listener.Listener { + options := listener.Options{} + for _, opt := range opts { + opt(&options) + } + return &tunListener{ + log: options.Logger, + options: options, + } +} + +func (l *tunListener) Init(md mdata.Metadata) (err error) { + if err = l.parseMetadata(md); err != nil { + return + } + + l.addr = &Addr{Name: l.options.Addr} + + l.cqueue = make(chan net.Conn) + l.closed = make(chan struct{}) + + go l.listenLoop() + + return +} + +func (l *tunListener) listenLoop() { + for { + ctx, cancel := context.WithCancel(context.Background()) + err := func() error { + ifce, name, ip, err := l.createTun() + if err != nil { + if ifce != nil { + ifce.Close() + } + return err + } + + itf, err := net.InterfaceByName(name) + if err != nil { + return err + } + + addrs, _ := itf.Addrs() + l.log.Infof("name: %s, net: %s, mtu: %d, addrs: %s", + itf.Name, ip, itf.MTU, addrs) + + var c net.Conn + c = &conn{ + ifce: ifce, + laddr: l.addr, + raddr: &net.IPAddr{IP: ip}, + cancel: cancel, + } + c = metrics.WrapConn(l.options.Service, c) + c = stats.WrapConn(c, l.options.Stats) + c = limiter_wrapper.WrapConn( + c, + l.options.TrafficLimiter, + traffic_limiter.ServiceLimitKey, + limiter.ScopeOption(limiter.ScopeService), + limiter.ServiceOption(l.options.Service), + limiter.NetworkOption(c.LocalAddr().Network()), + ) + l.cqueue <- c + + return nil + }() + if err != nil { + l.log.Error(err) + cancel() + } + + select { + case <-ctx.Done(): + case <-l.closed: + return + } + + time.Sleep(time.Second) + } +} + +func (l *tunListener) Accept() (net.Conn, error) { + select { + case conn := <-l.cqueue: + return conn, nil + case <-l.closed: + } + + return nil, listener.ErrClosed +} + +func (l *tunListener) Addr() net.Addr { + return l.addr +} + +func (l *tunListener) Close() error { + select { + case <-l.closed: + return net.ErrClosed + default: + close(l.closed) + } + return nil +} + +type Addr struct { + Name string +} + +func (a *Addr) Network() string { + return "tun" +} + +func (a *Addr) String() string { + return a.Name +} diff --git a/listener/vtun/metadata.go b/listener/vtun/metadata.go new file mode 100644 index 00000000..4d5966cf --- /dev/null +++ b/listener/vtun/metadata.go @@ -0,0 +1,93 @@ +package tun + +import ( + "net" + "strings" + + mdata "github.com/go-gost/core/metadata" + "github.com/go-gost/core/router" + tun_util "github.com/go-gost/x/internal/util/tun" + mdutil "github.com/go-gost/x/metadata/util" + xrouter "github.com/go-gost/x/router" +) + +const ( + defaultMTU = 1420 +) + +type metadata struct { + config *tun_util.Config +} + +func (l *tunListener) parseMetadata(md mdata.Metadata) (err error) { + config := &tun_util.Config{ + Name: mdutil.GetString(md, "name", "tun.name"), + Peer: mdutil.GetString(md, "peer", "tun.peer"), + MTU: mdutil.GetInt(md, "mtu", "tun.mtu"), + } + if config.MTU <= 0 { + config.MTU = defaultMTU + } + if gw := mdutil.GetString(md, "gw", "tun.gw"); gw != "" { + config.Gateway = net.ParseIP(gw) + } + + for _, s := range strings.Split(mdutil.GetString(md, "net", "tun.net"), ",") { + if s = strings.TrimSpace(s); s == "" { + continue + } + ip, ipNet, err := net.ParseCIDR(s) + if err != nil { + continue + } + config.Net = append(config.Net, net.IPNet{ + IP: ip, + Mask: ipNet.Mask, + }) + } + + for _, s := range strings.Split(mdutil.GetString(md, "route", "tun.route"), ",") { + gw := "" + if config.Gateway != nil { + gw = config.Gateway.String() + } + if route := xrouter.ParseRoute(strings.TrimSpace(s), gw); route != nil { + l.routes = append(l.routes, route) + } + } + + for _, s := range mdutil.GetStrings(md, "routes", "tun.routes") { + ss := strings.SplitN(s, " ", 2) + if len(ss) == 2 { + _, ipNet, _ := net.ParseCIDR(strings.TrimSpace(ss[0])) + if ipNet == nil { + continue + } + gw := net.ParseIP(ss[1]) + if gw == nil { + gw = config.Gateway + } + + gateway := "" + if gw != nil { + gateway = gw.String() + } + + l.routes = append(l.routes, &router.Route{ + Net: ipNet, + Dst: ipNet.String(), + Gateway: gateway, + }) + } + } + + for _, v := range strings.Split(mdutil.GetString(md, "dns", "tun.dns"), ",") { + if ip := net.ParseIP(strings.TrimSpace(v)); ip != nil { + config.DNS = append(config.DNS, ip) + } + } + + l.md.config = config + + return +} diff --git a/listener/vtun/tun.go b/listener/vtun/tun.go new file mode 100644 index 00000000..aae6d5ec --- /dev/null +++ b/listener/vtun/tun.go @@ -0,0 +1,91 @@ +package tun + +import ( + "io" + "math" + + "github.com/go-gost/core/logger" + "golang.zx2c4.com/wireguard/tun" +) + +type tunDevice struct { + dev tun.Device + + packets int + sizes []int + rbufs [][]byte + wbufs [][]byte + wbuf []byte + + log logger.Logger +} + +func (d *tunDevice) Read(p []byte) (n int, err error) { + if d.packets > 0 { + for i, size := range d.sizes { + if size > 0 { + n = copy(p, d.rbufs[i][:size]) + + d.sizes[i] = 0 + d.packets-- + return + } + } + } + + d.rbufs[0] = p + packets, err := d.dev.Read(d.rbufs, d.sizes, readOffset) + if err != nil && err != tun.ErrTooManySegments { + return + } + n = d.sizes[0] + + d.sizes[0] = 0 + d.packets = packets - 1 + + // d.log.Debugf("read tun: (%d), % x", n, p[:n]) + return +} + +func (d *tunDevice) Write(p []byte) (n int, err error) { + if writeOffset > 0 { + copy(d.wbuf[writeOffset:], p) + d.wbufs[0] = d.wbuf[:writeOffset+len(p)] + } else { + d.wbufs[0] = p + } + + _, err = d.dev.Write(d.wbufs, writeOffset) + n = len(p) + return +} + +func (d *tunDevice) Close() error { + return d.dev.Close() +} + +func (l *tunListener) createTunDevice() (dev io.ReadWriteCloser, name string, err error) { + ifce, err := tun.CreateTUN(l.md.config.Name, l.md.config.MTU) + if err != nil { + return + } + + batchSize := 16 + + rbufs := make([][]byte, batchSize) + for i := 1; i < len(rbufs); i++ { + rbufs[i] = make([]byte, math.MaxUint16) + } + + dev = &tunDevice{ + dev: ifce, + sizes: make([]int, batchSize), + rbufs: rbufs, + wbufs: make([][]byte, 1), + wbuf: make([]byte, math.MaxUint16+writeOffset), + log: l.log, + } + name, err = ifce.Name() + + return +} diff --git a/listener/vtun/tun_darwin.go b/listener/vtun/tun_darwin.go new file mode 100644 index 00000000..96db894b --- /dev/null +++ b/listener/vtun/tun_darwin.go @@ -0,0 +1,58 @@ +package tun + +import ( + "fmt" + "io" + "net" + "os/exec" + "strings" +) + +const ( + defaultTunName = "utun" + readOffset = 4 + writeOffset = 4 +) + +func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) { + if l.md.config.Name == "" { + l.md.config.Name = defaultTunName + } + ifce, name, err = l.createTunDevice() + if err != nil { + return + } + + peer := l.md.config.Peer + if peer == "" { + peer = ip.String() + } + if len(l.md.config.Net) > 0 { + cmd := fmt.Sprintf("ifconfig %s inet %s %s mtu %d up", + name, l.md.config.Net[0].String(), l.md.config.Peer, l.md.config.MTU) + l.logger.Debug(cmd) + args := strings.Split(cmd, " ") + if err = exec.Command(args[0], args[1:]...).Run(); err != nil { + return + } + ip = l.md.config.Net[0].IP + } + + if err = l.addRoutes(name); err != nil { + return + } + + return +} + +func (l *tunListener) addRoutes(ifName string) error { + for _, route := range l.routes { + cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName) + l.logger.Debug(cmd) + args := strings.Split(cmd, " ") + if err := exec.Command(args[0], args[1:]...).Run(); err != nil { + return err + } + } + return nil +} diff --git a/listener/vtun/tun_linux.go b/listener/vtun/tun_linux.go new file mode 100644 index 00000000..6302c26e --- /dev/null +++ b/listener/vtun/tun_linux.go @@ -0,0 +1,90 @@ +package tun + +import ( + "fmt" + "io" + "net" + "os/exec" + "strings" + + "github.com/vishvananda/netlink" +) + +const ( + readOffset = 0 + writeOffset = 16 +) + +func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.IP, err error) { + dev, name, err = l.createTunDevice() + if err != nil { + return + } + + ifce, err := net.InterfaceByName(name) + if err != nil { + return + } + + link, err := netlink.LinkByName(name) + if err != nil { + return + } + + for _, net := range l.md.config.Net { + if err = netlink.AddrAdd(link, &netlink.Addr{ + IPNet: &net, + }); err != nil { + l.log.Error(err) + continue + } + } + if len(l.md.config.Net) > 0 { + ip = l.md.config.Net[0].IP + } + + if err = netlink.LinkSetUp(link); err != nil { + return + } + + if err = l.addRoutes(ifce); err != nil { + return + } + + var dnsServers []string + for _, dns := range l.md.config.DNS { + dnsServers = append(dnsServers, dns.String()) + } + if len(dnsServers) > 0 { + cmd := fmt.Sprintf("resolvectl dns %s %s", name, strings.Join(dnsServers, " ")) + l.log.Debug(cmd) + + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + } + + return +} + +func (l *tunListener) addRoutes(ifce *net.Interface) error { + for _, route := range l.routes { + if route.Net == nil { + continue + } + + r := netlink.Route{ + Dst: route.Net, + Gw: net.ParseIP(route.Gateway), + } + if r.Gw == nil { + r.LinkIndex = ifce.Index + } + if err := netlink.RouteReplace(&r); err != nil { + return fmt.Errorf("add route %v %v: %v", r.Dst, r.Gw, err) + } + } + return nil +} diff --git a/listener/vtun/tun_unix.go b/listener/vtun/tun_unix.go new file mode 100644 index 00000000..087bb725 --- /dev/null +++ b/listener/vtun/tun_unix.go @@ -0,0 +1,58 @@ +//go:build !linux && !windows && !darwin + +package tun + +import ( + "fmt" + "io" + "net" + "os/exec" + "strings" +) + +const ( + defaultTunName = "tun0" + readOffset = 4 + writeOffset = 4 +) + +func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) { + if l.md.config.Name == "" { + l.md.config.Name = defaultTunName + } + ifce, name, err = l.createTunDevice() + if err != nil { + return + } + + if len(l.md.config.Net) > 0 { + cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", + name, l.md.config.Net[0].String(), l.md.config.MTU) + l.logger.Debug(cmd) + + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + ip = l.md.config.Net[0].IP + } + + if err = l.addRoutes(name); err != nil { + return + } + + return +} + +func (l *tunListener) addRoutes(ifName string) error { + for _, route := range l.routes { + cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName) + l.logger.Debug(cmd) + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + return fmt.Errorf("%s: %v", cmd, er) + } + } + return nil +} diff --git a/listener/vtun/tun_windows.go b/listener/vtun/tun_windows.go new file mode 100644 index 00000000..0562f83f --- /dev/null +++ b/listener/vtun/tun_windows.go @@ -0,0 +1,87 @@ +package tun + +import ( + "fmt" + "io" + "net" + "os/exec" + "strings" +) + +const ( + defaultTunName = "wintun" + readOffset = 0 + writeOffset = 0 +) + +func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) { + if l.md.config.Name == "" { + l.md.config.Name = defaultTunName + } + ifce, name, err = l.createTunDevice() + if err != nil { + return + } + + if len(l.md.config.Net) > 0 { + ipNet := l.md.config.Net[0] + cmd := fmt.Sprintf("netsh interface ip set address name=%s "+ + "source=static addr=%s mask=%s gateway=none", + name, ipNet.IP.String(), ipMask(ipNet.Mask)) + l.logger.Debug(cmd) + + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + ip = ipNet.IP + } + + if err = l.addRoutes(name, l.md.config.Gateway); err != nil { + return + } + + for _, dns := range l.md.config.DNS { + cmd := fmt.Sprintf("netsh interface ip add dnsservers name=%s address=%s validate=no", name, dns.String()) + l.logger.Debug(cmd) + + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + err = fmt.Errorf("%s: %v", cmd, er) + return + } + } + + return +} + +func (l *tunListener) addRoutes(ifName string, gw net.IP) error { + for _, route := range l.routes { + l.deleteRoute(ifName, route.Net.String()) + + cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active", + route.Net.String(), ifName) + if gw != nil { + cmd += " nexthop=" + gw.String() + } + l.logger.Debug(cmd) + args := strings.Split(cmd, " ") + if er := exec.Command(args[0], args[1:]...).Run(); er != nil { + return fmt.Errorf("%s: %v", cmd, er) + } + } + return nil +} + +func (l *tunListener) deleteRoute(ifName string, route string) error { + cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=%s store=active", + route, ifName) + l.logger.Debug(cmd) + args := strings.Split(cmd, " ") + return exec.Command(args[0], args[1:]...).Run() +} + +func ipMask(mask net.IPMask) string { + return fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]) +}