diff --git a/common/tun/gvisor.go b/common/tun/gvisor.go new file mode 100644 index 00000000..e493975a --- /dev/null +++ b/common/tun/gvisor.go @@ -0,0 +1,151 @@ +package tun + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" + "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" + "gvisor.dev/gvisor/pkg/waiter" +) + +const defaultNIC tcpip.NICID = 1 + +var _ adapter.Service = (*GVisorTun)(nil) + +type GVisorTun struct { + ctx context.Context + tunFd uintptr + tunMtu uint32 + handler Handler + stack *stack.Stack +} + +func NewGVisor(ctx context.Context, tunFd uintptr, tunMtu uint32, handler Handler) *GVisorTun { + return &GVisorTun{ + ctx: ctx, + tunFd: tunFd, + tunMtu: tunMtu, + handler: handler, + } +} + +func (t *GVisorTun) Start() error { + linkEndpoint, err := NewEndpoint(t.tunFd, t.tunMtu) + if err != nil { + return err + } + ipStack := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ + ipv4.NewProtocol, + ipv6.NewProtocol, + }, + TransportProtocols: []stack.TransportProtocolFactory{ + tcp.NewProtocol, + udp.NewProtocol, + icmp.NewProtocol4, + icmp.NewProtocol6, + }, + }) + tErr := ipStack.CreateNIC(defaultNIC, linkEndpoint) + if tErr != nil { + return E.New("create nic: ", tErr) + } + ipStack.SetRouteTable([]tcpip.Route{ + {Destination: header.IPv4EmptySubnet, NIC: defaultNIC}, + {Destination: header.IPv6EmptySubnet, NIC: defaultNIC}, + }) + ipStack.SetSpoofing(defaultNIC, true) + ipStack.SetPromiscuousMode(defaultNIC, true) + bufSize := 20 * 1024 + ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpip.TCPReceiveBufferSizeRangeOption{ + Min: 1, + Default: bufSize, + Max: bufSize, + }) + ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpip.TCPSendBufferSizeRangeOption{ + Min: 1, + Default: bufSize, + Max: bufSize, + }) + sOpt := tcpip.TCPSACKEnabled(true) + ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt) + mOpt := tcpip.TCPModerateReceiveBufferOption(true) + ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt) + tcpForwarder := tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) { + var wq waiter.Queue + endpoint, err := r.CreateEndpoint(&wq) + if err != nil { + r.Complete(true) + return + } + r.Complete(false) + endpoint.SocketOptions().SetKeepAlive(true) + tcpConn := gonet.NewTCPConn(&wq, endpoint) + lAddr := tcpConn.RemoteAddr() + rAddr := tcpConn.LocalAddr() + if lAddr == nil || rAddr == nil { + tcpConn.Close() + return + } + go func() { + var metadata M.Metadata + metadata.Source = M.SocksaddrFromNet(lAddr) + metadata.Destination = M.SocksaddrFromNet(rAddr) + ctx := log.ContextWithID(t.ctx) + hErr := t.handler.NewConnection(ctx, tcpConn, metadata) + if hErr != nil { + t.handler.NewError(ctx, hErr) + } + }() + }) + ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool { + return tcpForwarder.HandlePacket(id, buffer) + }) + udpForwarder := udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) { + var wq waiter.Queue + endpoint, err := request.CreateEndpoint(&wq) + if err != nil { + return + } + udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint) + lAddr := udpConn.RemoteAddr() + rAddr := udpConn.LocalAddr() + if lAddr == nil || rAddr == nil { + udpConn.Close() + return + } + go func() { + var metadata M.Metadata + metadata.Source = M.SocksaddrFromNet(lAddr) + metadata.Destination = M.SocksaddrFromNet(rAddr) + ctx := log.ContextWithID(t.ctx) + hErr := t.handler.NewPacketConnection(ctx, bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(udpConn), Addr: M.SocksaddrFromNet(rAddr)}), metadata) + if hErr != nil { + t.handler.NewError(ctx, hErr) + } + }() + }) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) + t.stack = ipStack + return nil +} + +func (t *GVisorTun) Close() error { + return common.Close( + common.PtrOrNil(t.stack), + ) +} diff --git a/common/tun/gvisor_linux.go b/common/tun/gvisor_linux.go new file mode 100644 index 00000000..f09171d1 --- /dev/null +++ b/common/tun/gvisor_linux.go @@ -0,0 +1,16 @@ +//go:build linux + +package tun + +import ( + "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +func NewEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) { + return fdbased.New(&fdbased.Options{ + FDs: []int{int(tunFd)}, + MTU: tunMtu, + PacketDispatchMode: fdbased.PacketMMap, + }) +} diff --git a/common/tun/gvisor_nonlinux.go b/common/tun/gvisor_nonlinux.go new file mode 100644 index 00000000..6fb5fd56 --- /dev/null +++ b/common/tun/gvisor_nonlinux.go @@ -0,0 +1,9 @@ +//go:build !linux + +package tun + +import "gvisor.dev/gvisor/pkg/tcpip/stack" + +func NewEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) { + return NewPosixEndpoint(tunFd, tunMtu) +} diff --git a/common/tun/gvisor_posix.go b/common/tun/gvisor_posix.go new file mode 100644 index 00000000..c70ebf16 --- /dev/null +++ b/common/tun/gvisor_posix.go @@ -0,0 +1,118 @@ +package tun + +import ( + "os" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/rw" + + gBuffer "gvisor.dev/gvisor/pkg/buffer" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +var _ stack.LinkEndpoint = (*PosixEndpoint)(nil) + +type PosixEndpoint struct { + fd uintptr + mtu uint32 + file *os.File + dispatcher stack.NetworkDispatcher +} + +func NewPosixEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) { + return &PosixEndpoint{ + fd: tunFd, + mtu: tunMtu, + file: os.NewFile(tunFd, "tun"), + }, nil +} + +func (e *PosixEndpoint) MTU() uint32 { + return e.mtu +} + +func (e *PosixEndpoint) MaxHeaderLength() uint16 { + return 0 +} + +func (e *PosixEndpoint) LinkAddress() tcpip.LinkAddress { + return "" +} + +func (e *PosixEndpoint) Capabilities() stack.LinkEndpointCapabilities { + return stack.CapabilityNone +} + +func (e *PosixEndpoint) Attach(dispatcher stack.NetworkDispatcher) { + if dispatcher == nil && e.dispatcher != nil { + e.dispatcher = nil + return + } + if dispatcher != nil && e.dispatcher == nil { + e.dispatcher = dispatcher + go e.dispatchLoop() + } +} + +func (e *PosixEndpoint) dispatchLoop() { + _buffer := buf.StackNewPacket() + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + for { + n, err := e.file.Read(buffer.FreeBytes()) + if err != nil { + break + } + var view gBuffer.View + view.Append(buffer.To(n)) + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: view, + IsForwardedPacket: true, + }) + defer pkt.DecRef() + var p tcpip.NetworkProtocolNumber + ipHeader, ok := pkt.Data().PullUp(1) + if !ok { + continue + } + switch header.IPVersion(ipHeader) { + case header.IPv4Version: + p = header.IPv4ProtocolNumber + case header.IPv6Version: + p = header.IPv6ProtocolNumber + default: + continue + } + e.dispatcher.DeliverNetworkPacket(p, pkt) + } +} + +func (e *PosixEndpoint) IsAttached() bool { + return e.dispatcher != nil +} + +func (e *PosixEndpoint) Wait() { +} + +func (e *PosixEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (e *PosixEndpoint) AddHeader(buffer *stack.PacketBuffer) { +} + +func (e *PosixEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) { + var n int + for _, packet := range pkts.AsSlice() { + _, err := rw.WriteV(e.fd, packet.Slices()) + if err != nil { + return n, &tcpip.ErrAborted{} + } + n++ + } + return n, nil +} diff --git a/common/tun/tun.go b/common/tun/tun.go new file mode 100644 index 00000000..ee5f4822 --- /dev/null +++ b/common/tun/tun.go @@ -0,0 +1,12 @@ +package tun + +import ( + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" +) + +type Handler interface { + N.TCPConnectionHandler + N.UDPConnectionHandler + E.Handler +} diff --git a/common/tun/tun_linux.go b/common/tun/tun_linux.go new file mode 100644 index 00000000..7cb6bf97 --- /dev/null +++ b/common/tun/tun_linux.go @@ -0,0 +1,51 @@ +package tun + +import ( + "net/netip" + + "github.com/vishvananda/netlink" + "gvisor.dev/gvisor/pkg/tcpip/link/tun" +) + +func Open(name string) (uintptr, error) { + tunFd, err := tun.Open(name) + if err != nil { + return 0, err + } + return uintptr(tunFd), nil +} + +func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32) error { + tunLink, err := netlink.LinkByName(name) + if err != nil { + return err + } + + if inet4Address.IsValid() { + addr4, _ := netlink.ParseAddr(inet4Address.String()) + err = netlink.AddrAdd(tunLink, addr4) + if err != nil { + return err + } + } + + if inet6Address.IsValid() { + addr6, _ := netlink.ParseAddr(inet6Address.String()) + err = netlink.AddrAdd(tunLink, addr6) + if err != nil { + return err + } + } + + err = netlink.LinkSetMTU(tunLink, int(mtu)) + if err != nil { + return err + } + + err = netlink.LinkSetUp(tunLink) + if err != nil { + return err + } + + return nil +} diff --git a/common/tun/tun_other.go b/common/tun/tun_other.go new file mode 100644 index 00000000..d2443e34 --- /dev/null +++ b/common/tun/tun_other.go @@ -0,0 +1,11 @@ +//go:build !linux + +package tun + +import ( + "os" +) + +func Open(name string) (uintptr, error) { + return 0, os.ErrInvalid +} diff --git a/constant/cgo.go b/constant/cgo.go index d6ce7035..6ec4dee7 100644 --- a/constant/cgo.go +++ b/constant/cgo.go @@ -1,3 +1,5 @@ +//go:build cgo + package constant const CGO_ENABLED = true diff --git a/constant/proxy.go b/constant/proxy.go index e5b0569f..ec7dddd1 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -7,4 +7,5 @@ const ( TypeHTTP = "http" TypeMixed = "mixed" TypeShadowsocks = "shadowsocks" + TypeTun = "tun" ) diff --git a/go.mod b/go.mod index ffe16fdb..26db5b86 100644 --- a/go.mod +++ b/go.mod @@ -7,23 +7,28 @@ require ( github.com/goccy/go-json v0.9.8 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oschwald/maxminddb-golang v1.9.0 - github.com/sagernet/sing v0.0.0-20220709022704-8843420c7814 + github.com/sagernet/sing v0.0.0-20220709090827-3e39af603559 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.0 + github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d - golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 + golang.org/x/net v0.0.0-20220708220712-1185a9018129 + gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/btree v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect diff --git a/go.sum b/go.sum index 13947215..4057396e 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -23,8 +25,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220709022704-8843420c7814 h1:CqrZT6p8TeMaJfys/0Scl9lO5gu1VccUrBw8fAq3ZIk= -github.com/sagernet/sing v0.0.0-20220709022704-8843420c7814/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= +github.com/sagernet/sing v0.0.0-20220709090827-3e39af603559 h1:o5/fAARzVV2PkEDzjOct1FRUdWVw7hPDFYY8drulH50= +github.com/sagernet/sing v0.0.0-20220709090827-3e39af603559/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= @@ -39,13 +41,20 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 h1:7SWt9pGCMaw+N1ZhRsaLKaYNviFhxambdoaoYlDqz1w= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM= -golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= +golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -53,5 +62,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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-20220708233959-72bdef768f07 h1:EBa4BJfuxvdpvMGq6gw347MDptuFgqDHhuTEKxCCQ5U= +gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= diff --git a/inbound/builder.go b/inbound/builder.go index 251a2cf6..1e60ef69 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -34,6 +34,8 @@ func New(ctx context.Context, router adapter.Router, logger log.Logger, index in return NewMixed(ctx, router, inboundLogger, options.Tag, options.MixedOptions), nil case C.TypeShadowsocks: return NewShadowsocks(ctx, router, inboundLogger, options.Tag, options.ShadowsocksOptions) + case C.TypeTun: + return NewTun(ctx, router, inboundLogger, options.Tag, options.TunOptions) default: return nil, E.New("unknown inbound type: ", options.Type) } diff --git a/inbound/default.go b/inbound/default.go index 9096a10a..db8add65 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -231,12 +231,16 @@ func (a *myInboundAdapter) newError(err error) { } func (a *myInboundAdapter) NewError(ctx context.Context, err error) { + NewError(a.logger, ctx, err) +} + +func NewError(logger log.Logger, ctx context.Context, err error) { common.Close(err) if E.IsClosed(err) { - a.logger.WithContext(ctx).Debug("connection closed") + logger.WithContext(ctx).Debug("connection closed") return } - a.logger.WithContext(ctx).Error(err) + logger.WithContext(ctx).Error(err) } func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { diff --git a/inbound/tun.go b/inbound/tun.go new file mode 100644 index 00000000..7db67fab --- /dev/null +++ b/inbound/tun.go @@ -0,0 +1,142 @@ +//go:build !no_tun + +package inbound + +import ( + "context" + "net" + "net/netip" + "os" + "runtime" + "strconv" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tun" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.Inbound = (*Tun)(nil) + +type Tun struct { + tag string + + ctx context.Context + router adapter.Router + logger log.Logger + options option.TunInboundOptions + + tunFd uintptr + tun *tun.GVisorTun +} + +func NewTun(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.TunInboundOptions) (*Tun, error) { + return &Tun{ + tag: tag, + ctx: ctx, + router: router, + logger: logger, + options: options, + }, nil +} + +func (t *Tun) Type() string { + return C.TypeTun +} + +func (t *Tun) Tag() string { + return t.tag +} + +func (t *Tun) Start() error { + tunName := t.options.InterfaceName + if tunName == "" { + tunName = mkInterfaceName() + } + var mtu uint32 + if t.options.MTU != 0 { + mtu = t.options.MTU + } else { + mtu = 1500 + } + + tunFd, err := tun.Open(tunName) + if err != nil { + return E.Cause(err, "create tun interface") + } + err = tun.Configure(tunName, netip.Prefix(t.options.Inet4Address), netip.Prefix(t.options.Inet6Address), mtu) + if err != nil { + return E.Cause(err, "configure tun interface") + } + t.tunFd = tunFd + t.tun = tun.NewGVisor(t.ctx, tunFd, mtu, t) + err = t.tun.Start() + if err != nil { + return err + } + t.logger.Info("started at ", tunName) + return nil +} + +func (t *Tun) Close() error { + return E.Errors( + t.tun.Close(), + os.NewFile(t.tunFd, "tun").Close(), + ) +} + +func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { + t.logger.WithContext(ctx).Info("inbound connection from ", upstreamMetadata.Source) + t.logger.WithContext(ctx).Info("inbound connection to ", upstreamMetadata.Destination) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.Network = C.NetworkTCP + metadata.Source = upstreamMetadata.Source + metadata.Destination = upstreamMetadata.Destination + return t.router.RouteConnection(ctx, conn, metadata) +} + +func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { + t.logger.WithContext(ctx).Info("inbound packet connection from ", upstreamMetadata.Source) + t.logger.WithContext(ctx).Info("inbound packet connection to ", upstreamMetadata.Destination) + var metadata adapter.InboundContext + metadata.Inbound = t.tag + metadata.Network = C.NetworkUDP + metadata.Source = upstreamMetadata.Source + metadata.Destination = upstreamMetadata.Destination + return t.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (t *Tun) NewError(ctx context.Context, err error) { + NewError(t.logger, ctx, err) +} + +func mkInterfaceName() (tunName string) { + switch runtime.GOOS { + case "darwin": + tunName = "utun" + default: + tunName = "tun" + } + interfaces, err := net.Interfaces() + if err != nil { + return + } + var tunIndex int + for _, netInterface := range interfaces { + if strings.HasPrefix(netInterface.Name, tunName) { + index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16) + if parseErr == nil { + tunIndex = int(index) + 1 + } + } + } + tunName = F.ToString(tunName, tunIndex) + return +} diff --git a/inbound/tun_disabled.go b/inbound/tun_disabled.go new file mode 100644 index 00000000..6c21a265 --- /dev/null +++ b/inbound/tun_disabled.go @@ -0,0 +1,16 @@ +//go:build no_tun + +package inbound + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func NewTun(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { + return nil, E.New("tun disabled in this build") +} diff --git a/option/inbound.go b/option/inbound.go index d3e64e08..9021ca49 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -17,6 +17,7 @@ type _Inbound struct { HTTPOptions SimpleInboundOptions `json:"-"` MixedOptions SimpleInboundOptions `json:"-"` ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` + TunOptions TunInboundOptions `json:"-"` } type Inbound _Inbound @@ -28,7 +29,8 @@ func (h Inbound) Equals(other Inbound) bool { h.SocksOptions.Equals(other.SocksOptions) && h.HTTPOptions.Equals(other.HTTPOptions) && h.MixedOptions.Equals(other.MixedOptions) && - h.ShadowsocksOptions.Equals(other.ShadowsocksOptions) + h.ShadowsocksOptions.Equals(other.ShadowsocksOptions) && + h.TunOptions == other.TunOptions } func (h Inbound) MarshalJSON() ([]byte, error) { @@ -44,6 +46,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) { v = h.MixedOptions case C.TypeShadowsocks: v = h.ShadowsocksOptions + case C.TypeTun: + v = h.TunOptions default: return nil, E.New("unknown inbound type: ", h.Type) } @@ -67,6 +71,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { v = &h.MixedOptions case C.TypeShadowsocks: v = &h.ShadowsocksOptions + case C.TypeTun: + v = &h.TunOptions default: return nil } @@ -132,3 +138,10 @@ type ShadowsocksDestination struct { Password string `json:"password"` ServerOptions } + +type TunInboundOptions struct { + InterfaceName string `json:"interface_name"` + MTU uint32 `json:"mtu,omitempty"` + Inet4Address ListenPrefix `json:"inet4_address"` + Inet6Address ListenPrefix `json:"inet6_address"` +} diff --git a/option/types.go b/option/types.go index 3f2b2a51..9825e604 100644 --- a/option/types.go +++ b/option/types.go @@ -155,3 +155,27 @@ func (d *Duration) UnmarshalJSON(bytes []byte) error { *d = Duration(duration) return nil } + +type ListenPrefix netip.Prefix + +func (p ListenPrefix) MarshalJSON() ([]byte, error) { + prefix := netip.Prefix(p) + if !prefix.IsValid() { + return json.Marshal("") + } + return json.Marshal(prefix.String()) +} + +func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { + var value string + err := json.Unmarshal(bytes, &value) + if err != nil { + return err + } + prefix, err := netip.ParsePrefix(value) + if err != nil { + return err + } + *p = ListenPrefix(prefix) + return nil +} diff --git a/test/go.mod b/test/go.mod index 2ad7d7fc..ee9ece63 100644 --- a/test/go.mod +++ b/test/go.mod @@ -5,11 +5,11 @@ go 1.18 require ( github.com/docker/docker v20.10.17+incompatible github.com/docker/go-connections v0.4.0 - github.com/sagernet/sing v0.0.0-20220709022704-8843420c7814 + github.com/sagernet/sing v0.0.0-20220709090827-3e39af603559 github.com/sagernet/sing-box v0.0.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.8.0 - golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 + golang.org/x/net v0.0.0-20220708220712-1185a9018129 ) replace github.com/sagernet/sing-box => ../ @@ -22,6 +22,7 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/goccy/go-json v0.9.8 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.0.1 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect @@ -33,10 +34,13 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 // indirect + github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect + golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.3.0 // indirect + gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07 // indirect lukechampine.com/blake3 v1.1.7 // indirect ) diff --git a/test/go.sum b/test/go.sum index 3f3fc958..a1c73fcb 100644 --- a/test/go.sum +++ b/test/go.sum @@ -21,10 +21,12 @@ github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -50,8 +52,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sagernet/sing v0.0.0-20220709022704-8843420c7814 h1:CqrZT6p8TeMaJfys/0Scl9lO5gu1VccUrBw8fAq3ZIk= -github.com/sagernet/sing v0.0.0-20220709022704-8843420c7814/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= +github.com/sagernet/sing v0.0.0-20220709090827-3e39af603559 h1:o5/fAARzVV2PkEDzjOct1FRUdWVw7hPDFYY8drulH50= +github.com/sagernet/sing v0.0.0-20220709090827-3e39af603559/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -64,6 +66,10 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 h1:7SWt9pGCMaw+N1ZhRsaLKaYNviFhxambdoaoYlDqz1w= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -78,20 +84,21 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM= -golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= +golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= @@ -114,5 +121,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07 h1:EBa4BJfuxvdpvMGq6gw347MDptuFgqDHhuTEKxCCQ5U= +gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=