Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6a8c23d65 | ||
|
|
8bf4113343 | ||
|
|
9c6e70b7cc | ||
|
|
d744d03d93 | ||
|
|
53f50347e0 | ||
|
|
bf7110b1ab | ||
|
|
d880656b52 | ||
|
|
499c0aed67 | ||
|
|
8848c0e4cb | ||
|
|
980d4bf9a3 | ||
|
|
ca53ccf346 | ||
|
|
f0c27d037c | ||
|
|
35d565af65 | ||
|
|
45b089e6bb | ||
|
|
56bedd2f05 | ||
|
|
fe89bbded2 | ||
|
|
839f1792e4 | ||
|
|
20226b91c9 | ||
|
|
8507bb3a0a | ||
|
|
0cdb0eed74 |
4
Makefile
4
Makefile
@@ -1,11 +1,11 @@
|
||||
fmt:
|
||||
@gofumpt -l -w .
|
||||
@gofmt -s -w .
|
||||
@gci write -s "standard,prefix(github.com/sagernet/),default" .
|
||||
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
|
||||
|
||||
fmt_install:
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go install -v github.com/daixiang0/gci@v0.4.0
|
||||
go install -v github.com/daixiang0/gci@latest
|
||||
|
||||
lint:
|
||||
GOOS=linux golangci-lint run .
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Simple transparent proxy library.
|
||||
|
||||
For Linux, Windows and macOS.
|
||||
For Linux, Windows, macOS and iOS.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
6
go.mod
6
go.mod
@@ -6,9 +6,9 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.1.2
|
||||
golang.org/x/net v0.4.0
|
||||
golang.org/x/sys v0.3.0
|
||||
github.com/sagernet/sing v0.2.4
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/sys v0.7.0
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
||||
)
|
||||
|
||||
|
||||
14
go.sum
14
go.sum
@@ -2,24 +2,22 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.1.2 h1:rp5AqY23P0klk2IaLEI0/WJsD8FTVlv9TaI2QSL6TDA=
|
||||
github.com/sagernet/sing v0.1.2/go.mod h1:bvmen56QnVbMrWy+nr5nsbz7U5MUPuY0L0S/XfhCsTs=
|
||||
github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
|
||||
github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
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/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/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=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
|
||||
102
gvisor.go
102
gvisor.go
@@ -4,10 +4,14 @@ package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/canceler"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
@@ -32,9 +36,12 @@ type GVisor struct {
|
||||
tunMtu uint32
|
||||
endpointIndependentNat bool
|
||||
udpTimeout int64
|
||||
router Router
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
stack *stack.Stack
|
||||
endpoint stack.LinkEndpoint
|
||||
routeMapping *RouteMapping
|
||||
}
|
||||
|
||||
type GVisorTun interface {
|
||||
@@ -50,14 +57,20 @@ func NewGVisor(
|
||||
return nil, E.New("gVisor stack is unsupported on current platform")
|
||||
}
|
||||
|
||||
return &GVisor{
|
||||
gStack := &GVisor{
|
||||
ctx: options.Context,
|
||||
tun: gTun,
|
||||
tunMtu: options.MTU,
|
||||
endpointIndependentNat: options.EndpointIndependentNat,
|
||||
udpTimeout: options.UDPTimeout,
|
||||
router: options.Router,
|
||||
handler: options.Handler,
|
||||
}, nil
|
||||
logger: options.Logger,
|
||||
}
|
||||
if gStack.router != nil {
|
||||
gStack.routeMapping = NewRouteMapping(options.UDPTimeout)
|
||||
}
|
||||
return gStack, nil
|
||||
}
|
||||
|
||||
func (t *GVisor) Start() error {
|
||||
@@ -103,7 +116,7 @@ func (t *GVisor) Start() error {
|
||||
mOpt := tcpip.TCPModerateReceiveBufferOption(true)
|
||||
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
|
||||
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) {
|
||||
tcpForwarder := tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
handshakeCtx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
@@ -141,10 +154,47 @@ func (t *GVisor) Start() error {
|
||||
endpoint.Abort()
|
||||
}
|
||||
}()
|
||||
}).HandlePacket)
|
||||
})
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool {
|
||||
if t.router != nil {
|
||||
var routeSession RouteSession
|
||||
routeSession.Network = syscall.IPPROTO_TCP
|
||||
var ipHdr header.Network
|
||||
if buffer.NetworkProtocolNumber == header.IPv4ProtocolNumber {
|
||||
routeSession.IPVersion = 4
|
||||
ipHdr = header.IPv4(buffer.NetworkHeader().Slice())
|
||||
} else {
|
||||
routeSession.IPVersion = 6
|
||||
ipHdr = header.IPv6(buffer.NetworkHeader().Slice())
|
||||
}
|
||||
tcpHdr := header.TCP(buffer.TransportHeader().Slice())
|
||||
routeSession.Source = M.AddrPortFrom(net.IP(ipHdr.SourceAddress()), tcpHdr.SourcePort())
|
||||
routeSession.Destination = M.AddrPortFrom(net.IP(ipHdr.DestinationAddress()), tcpHdr.DestinationPort())
|
||||
action := t.routeMapping.Lookup(routeSession, func() RouteAction {
|
||||
if routeSession.IPVersion == 4 {
|
||||
return t.router.RouteConnection(routeSession, &systemTCPDirectPacketWriter4{t.tun, routeSession.Source})
|
||||
} else {
|
||||
return t.router.RouteConnection(routeSession, &systemTCPDirectPacketWriter6{t.tun, routeSession.Source})
|
||||
}
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send icmp unreachable
|
||||
return true
|
||||
case *ActionDirect:
|
||||
buffer.IncRef()
|
||||
err = actionType.WritePacketBuffer(buffer)
|
||||
if err != nil {
|
||||
t.logger.Trace("route gvisor tcp packet: ", err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return tcpForwarder.HandlePacket(id, buffer)
|
||||
})
|
||||
|
||||
if !t.endpointIndependentNat {
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) {
|
||||
udpForwarder := udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
endpoint, err := request.CreateEndpoint(&wq)
|
||||
if err != nil {
|
||||
@@ -161,12 +211,50 @@ func (t *GVisor) Start() error {
|
||||
var metadata M.Metadata
|
||||
metadata.Source = M.SocksaddrFromNet(lAddr)
|
||||
metadata.Destination = M.SocksaddrFromNet(rAddr)
|
||||
hErr := t.handler.NewPacketConnection(ContextWithNeedTimeout(t.ctx, true), bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(&gUDPConn{udpConn}), Addr: M.SocksaddrFromNet(rAddr)}), metadata)
|
||||
ctx, conn := canceler.NewPacketConn(t.ctx, bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(&gUDPConn{udpConn}), Addr: M.SocksaddrFromNet(rAddr)}), time.Duration(t.udpTimeout)*time.Second)
|
||||
hErr := t.handler.NewPacketConnection(ctx, conn, metadata)
|
||||
if hErr != nil {
|
||||
endpoint.Abort()
|
||||
}
|
||||
}()
|
||||
}).HandlePacket)
|
||||
})
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool {
|
||||
if t.router != nil {
|
||||
var routeSession RouteSession
|
||||
routeSession.Network = syscall.IPPROTO_UDP
|
||||
var ipHdr header.Network
|
||||
if buffer.NetworkProtocolNumber == header.IPv4ProtocolNumber {
|
||||
routeSession.IPVersion = 4
|
||||
ipHdr = header.IPv4(buffer.NetworkHeader().Slice())
|
||||
} else {
|
||||
routeSession.IPVersion = 6
|
||||
ipHdr = header.IPv6(buffer.NetworkHeader().Slice())
|
||||
}
|
||||
udpHdr := header.UDP(buffer.TransportHeader().Slice())
|
||||
routeSession.Source = M.AddrPortFrom(net.IP(ipHdr.SourceAddress()), udpHdr.SourcePort())
|
||||
routeSession.Destination = M.AddrPortFrom(net.IP(ipHdr.DestinationAddress()), udpHdr.DestinationPort())
|
||||
action := t.routeMapping.Lookup(routeSession, func() RouteAction {
|
||||
if routeSession.IPVersion == 4 {
|
||||
return t.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter4{t.tun, routeSession.Source})
|
||||
} else {
|
||||
return t.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter6{t.tun, routeSession.Source})
|
||||
}
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send icmp unreachable
|
||||
return true
|
||||
case *ActionDirect:
|
||||
buffer.IncRef()
|
||||
err = actionType.WritePacketBuffer(buffer)
|
||||
if err != nil {
|
||||
t.logger.Trace("route gvisor udp packet: ", err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return udpForwarder.HandlePacket(id, buffer)
|
||||
})
|
||||
} else {
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, NewUDPForwarder(t.ctx, ipStack, t.handler, t.udpTimeout).HandlePacket)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -83,29 +84,114 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var defaultInterface *net.Interface
|
||||
for _, rawRouteMessage := range routeMessages {
|
||||
routeMessage := rawRouteMessage.(*route.RouteMessage)
|
||||
if common.Any(common.FilterIsInstance(routeMessage.Addrs, func(it route.Addr) (*route.Inet4Addr, bool) {
|
||||
addr, loaded := it.(*route.Inet4Addr)
|
||||
return addr, loaded
|
||||
}), func(addr *route.Inet4Addr) bool {
|
||||
return addr.IP == netip.IPv4Unspecified().As4()
|
||||
}) {
|
||||
oldInterface := m.defaultInterfaceName
|
||||
oldIndex := m.defaultInterfaceIndex
|
||||
|
||||
m.defaultInterfaceIndex = routeMessage.Index
|
||||
defaultInterface, err := net.InterfaceByIndex(routeMessage.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.defaultInterfaceName = defaultInterface.Name
|
||||
if oldInterface == m.defaultInterfaceName && oldIndex == m.defaultInterfaceIndex {
|
||||
return nil
|
||||
}
|
||||
m.emit(EventInterfaceUpdate)
|
||||
return nil
|
||||
if len(routeMessage.Addrs) <= unix.RTAX_NETMASK {
|
||||
continue
|
||||
}
|
||||
destination, isIPv4Destination := routeMessage.Addrs[unix.RTAX_DST].(*route.Inet4Addr)
|
||||
if !isIPv4Destination {
|
||||
continue
|
||||
}
|
||||
if destination.IP != netip.IPv4Unspecified().As4() {
|
||||
continue
|
||||
}
|
||||
mask, isIPv4Mask := routeMessage.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr)
|
||||
if !isIPv4Mask {
|
||||
continue
|
||||
}
|
||||
ones, _ := net.IPMask(mask.IP[:]).Size()
|
||||
if ones != 0 {
|
||||
continue
|
||||
}
|
||||
routeInterface, err := net.InterfaceByIndex(routeMessage.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_UP == 0 {
|
||||
continue
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
||||
continue
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_IFSCOPE != 0 {
|
||||
continue
|
||||
}
|
||||
defaultInterface = routeInterface
|
||||
break
|
||||
}
|
||||
if defaultInterface == nil {
|
||||
defaultInterface, err = getDefaultInterfaceBySocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ErrNoRoute
|
||||
oldInterface := m.defaultInterfaceName
|
||||
oldIndex := m.defaultInterfaceIndex
|
||||
m.defaultInterfaceIndex = defaultInterface.Index
|
||||
m.defaultInterfaceName = defaultInterface.Name
|
||||
if oldInterface == m.defaultInterfaceName && oldIndex == m.defaultInterfaceIndex {
|
||||
return nil
|
||||
}
|
||||
m.emit(EventInterfaceUpdate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDefaultInterfaceBySocket() (*net.Interface, error) {
|
||||
socketFd, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create file descriptor")
|
||||
}
|
||||
defer unix.Close(socketFd)
|
||||
go unix.Connect(socketFd, &unix.SockaddrInet4{
|
||||
Addr: [4]byte{10, 255, 255, 255},
|
||||
Port: 80,
|
||||
})
|
||||
result := make(chan netip.Addr, 1)
|
||||
go func() {
|
||||
for {
|
||||
sockname, sockErr := unix.Getsockname(socketFd)
|
||||
if sockErr != nil {
|
||||
break
|
||||
}
|
||||
sockaddr, isInet4Sockaddr := sockname.(*unix.SockaddrInet4)
|
||||
if !isInet4Sockaddr {
|
||||
break
|
||||
}
|
||||
addr := netip.AddrFrom4(sockaddr.Addr)
|
||||
if addr.IsUnspecified() {
|
||||
time.Sleep(time.Millisecond)
|
||||
continue
|
||||
}
|
||||
result <- addr
|
||||
break
|
||||
}
|
||||
}()
|
||||
var selectedAddr netip.Addr
|
||||
select {
|
||||
case selectedAddr = <-result:
|
||||
case <-time.After(time.Second):
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "net.Interfaces")
|
||||
}
|
||||
for _, netInterface := range interfaces {
|
||||
interfaceAddrs, err := netInterface.Addrs()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "net.Interfaces.Addrs")
|
||||
}
|
||||
for _, interfaceAddr := range interfaceAddrs {
|
||||
ipNet, isIPNet := interfaceAddr.(*net.IPNet)
|
||||
if !isIPNet {
|
||||
continue
|
||||
}
|
||||
if ipNet.Contains(selectedAddr.AsSlice()) {
|
||||
return &netInterface, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, E.New("no interface found for address ", selectedAddr)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
var index int
|
||||
|
||||
for _, row := range rows {
|
||||
if row.DestinationPrefix.PrefixLength != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ifrow, err := row.InterfaceLUID.Interface()
|
||||
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
|
||||
continue
|
||||
|
||||
41
network_name.go
Normal file
41
network_name.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/clashtcpip"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func NetworkName(network uint8) string {
|
||||
switch network {
|
||||
case clashtcpip.TCP:
|
||||
return N.NetworkTCP
|
||||
case clashtcpip.UDP:
|
||||
return N.NetworkUDP
|
||||
case clashtcpip.ICMP:
|
||||
return N.NetworkICMPv4
|
||||
case clashtcpip.ICMPv6:
|
||||
return N.NetworkICMPv6
|
||||
}
|
||||
return F.ToString(network)
|
||||
}
|
||||
|
||||
func NetworkFromName(name string) uint8 {
|
||||
switch name {
|
||||
case N.NetworkTCP:
|
||||
return clashtcpip.TCP
|
||||
case N.NetworkUDP:
|
||||
return clashtcpip.UDP
|
||||
case N.NetworkICMPv4:
|
||||
return clashtcpip.ICMP
|
||||
case N.NetworkICMPv6:
|
||||
return clashtcpip.ICMPv6
|
||||
}
|
||||
parseNetwork, err := strconv.ParseUint(name, 10, 8)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return uint8(parseNetwork)
|
||||
}
|
||||
92
route.go
Normal file
92
route.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type ActionType = uint8
|
||||
|
||||
const (
|
||||
ActionTypeUnknown ActionType = iota
|
||||
ActionTypeReturn
|
||||
ActionTypeBlock
|
||||
ActionTypeDirect
|
||||
)
|
||||
|
||||
func ParseActionType(action string) (ActionType, error) {
|
||||
switch action {
|
||||
case "return":
|
||||
return ActionTypeReturn, nil
|
||||
case "block":
|
||||
return ActionTypeBlock, nil
|
||||
case "direct":
|
||||
return ActionTypeDirect, nil
|
||||
default:
|
||||
return 0, E.New("unknown action: ", action)
|
||||
}
|
||||
}
|
||||
|
||||
func ActionTypeName(actionType ActionType) (string, error) {
|
||||
switch actionType {
|
||||
case ActionTypeUnknown:
|
||||
return "", nil
|
||||
case ActionTypeReturn:
|
||||
return "return", nil
|
||||
case ActionTypeBlock:
|
||||
return "block", nil
|
||||
case ActionTypeDirect:
|
||||
return "direct", nil
|
||||
default:
|
||||
return "", E.New("unknown action: ", actionType)
|
||||
}
|
||||
}
|
||||
|
||||
type RouteSession struct {
|
||||
IPVersion uint8
|
||||
Network uint8
|
||||
Source netip.AddrPort
|
||||
Destination netip.AddrPort
|
||||
}
|
||||
|
||||
type RouteContext interface {
|
||||
WritePacket(packet []byte) error
|
||||
}
|
||||
|
||||
type Router interface {
|
||||
RouteConnection(session RouteSession, context RouteContext) RouteAction
|
||||
}
|
||||
|
||||
type RouteAction interface {
|
||||
ActionType() ActionType
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
type ActionReturn struct{}
|
||||
|
||||
func (r *ActionReturn) ActionType() ActionType {
|
||||
return ActionTypeReturn
|
||||
}
|
||||
|
||||
func (r *ActionReturn) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type ActionBlock struct{}
|
||||
|
||||
func (r *ActionBlock) ActionType() ActionType {
|
||||
return ActionTypeBlock
|
||||
}
|
||||
|
||||
func (r *ActionBlock) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type ActionDirect struct {
|
||||
DirectDestination
|
||||
}
|
||||
|
||||
func (r *ActionDirect) ActionType() ActionType {
|
||||
return ActionTypeDirect
|
||||
}
|
||||
16
route_gvisor.go
Normal file
16
route_gvisor.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type DirectDestination interface {
|
||||
WritePacket(buffer *buf.Buffer) error
|
||||
WritePacketBuffer(buffer *stack.PacketBuffer) error
|
||||
Close() error
|
||||
Timeout() bool
|
||||
}
|
||||
32
route_mapping.go
Normal file
32
route_mapping.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/cache"
|
||||
)
|
||||
|
||||
type RouteMapping struct {
|
||||
status *cache.LruCache[RouteSession, RouteAction]
|
||||
}
|
||||
|
||||
func NewRouteMapping(maxAge int64) *RouteMapping {
|
||||
return &RouteMapping{
|
||||
status: cache.New(
|
||||
cache.WithAge[RouteSession, RouteAction](maxAge),
|
||||
cache.WithUpdateAgeOnGet[RouteSession, RouteAction](),
|
||||
cache.WithEvict[RouteSession, RouteAction](func(key RouteSession, conn RouteAction) {
|
||||
common.Close(conn)
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RouteMapping) Lookup(session RouteSession, constructor func() RouteAction) RouteAction {
|
||||
action, _ := m.status.LoadOrStore(session, constructor)
|
||||
if action.Timeout() {
|
||||
common.Close(action)
|
||||
action = constructor()
|
||||
m.status.Store(session, action)
|
||||
}
|
||||
return action
|
||||
}
|
||||
119
route_nat.go
Normal file
119
route_nat.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/clashtcpip"
|
||||
)
|
||||
|
||||
type NatMapping struct {
|
||||
access sync.RWMutex
|
||||
sessions map[RouteSession]RouteContext
|
||||
ipRewrite bool
|
||||
}
|
||||
|
||||
func NewNatMapping(ipRewrite bool) *NatMapping {
|
||||
return &NatMapping{
|
||||
sessions: make(map[RouteSession]RouteContext),
|
||||
ipRewrite: ipRewrite,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *NatMapping) CreateSession(session RouteSession, context RouteContext) {
|
||||
if m.ipRewrite {
|
||||
session.Source = netip.AddrPort{}
|
||||
}
|
||||
m.access.Lock()
|
||||
m.sessions[session] = context
|
||||
m.access.Unlock()
|
||||
}
|
||||
|
||||
func (m *NatMapping) DeleteSession(session RouteSession) {
|
||||
if m.ipRewrite {
|
||||
session.Source = netip.AddrPort{}
|
||||
}
|
||||
m.access.Lock()
|
||||
delete(m.sessions, session)
|
||||
m.access.Unlock()
|
||||
}
|
||||
|
||||
func (m *NatMapping) WritePacket(packet []byte) (bool, error) {
|
||||
var routeSession RouteSession
|
||||
var ipHdr clashtcpip.IP
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
routeSession.IPVersion = 4
|
||||
ipHdr = clashtcpip.IPv4Packet(packet)
|
||||
case 6:
|
||||
routeSession.IPVersion = 6
|
||||
ipHdr = clashtcpip.IPv6Packet(packet)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
routeSession.Network = ipHdr.Protocol()
|
||||
switch routeSession.Network {
|
||||
case clashtcpip.TCP:
|
||||
tcpHdr := clashtcpip.TCPPacket(ipHdr.Payload())
|
||||
routeSession.Destination = netip.AddrPortFrom(ipHdr.SourceIP(), tcpHdr.SourcePort())
|
||||
if !m.ipRewrite {
|
||||
routeSession.Source = netip.AddrPortFrom(ipHdr.DestinationIP(), tcpHdr.DestinationPort())
|
||||
}
|
||||
case clashtcpip.UDP:
|
||||
udpHdr := clashtcpip.UDPPacket(ipHdr.Payload())
|
||||
routeSession.Destination = netip.AddrPortFrom(ipHdr.SourceIP(), udpHdr.SourcePort())
|
||||
if !m.ipRewrite {
|
||||
routeSession.Source = netip.AddrPortFrom(ipHdr.DestinationIP(), udpHdr.DestinationPort())
|
||||
}
|
||||
default:
|
||||
routeSession.Destination = netip.AddrPortFrom(ipHdr.SourceIP(), 0)
|
||||
if !m.ipRewrite {
|
||||
routeSession.Source = netip.AddrPortFrom(ipHdr.DestinationIP(), 0)
|
||||
}
|
||||
}
|
||||
m.access.RLock()
|
||||
context, loaded := m.sessions[routeSession]
|
||||
m.access.RUnlock()
|
||||
if !loaded {
|
||||
return false, nil
|
||||
}
|
||||
return true, context.WritePacket(packet)
|
||||
}
|
||||
|
||||
type NatWriter struct {
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
}
|
||||
|
||||
func NewNatWriter(inet4Address netip.Addr, inet6Address netip.Addr) *NatWriter {
|
||||
return &NatWriter{
|
||||
inet4Address: inet4Address,
|
||||
inet6Address: inet6Address,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *NatWriter) RewritePacket(packet []byte) {
|
||||
var ipHdr clashtcpip.IP
|
||||
var bindAddr netip.Addr
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
ipHdr = clashtcpip.IPv4Packet(packet)
|
||||
bindAddr = w.inet4Address
|
||||
case 6:
|
||||
ipHdr = clashtcpip.IPv6Packet(packet)
|
||||
bindAddr = w.inet6Address
|
||||
default:
|
||||
return
|
||||
}
|
||||
ipHdr.SetSourceIP(bindAddr)
|
||||
switch ipHdr.Protocol() {
|
||||
case clashtcpip.TCP:
|
||||
tcpHdr := clashtcpip.TCPPacket(ipHdr.Payload())
|
||||
tcpHdr.ResetChecksum(ipHdr.PseudoSum())
|
||||
case clashtcpip.UDP:
|
||||
udpHdr := clashtcpip.UDPPacket(ipHdr.Payload())
|
||||
udpHdr.ResetChecksum(ipHdr.PseudoSum())
|
||||
default:
|
||||
}
|
||||
ipHdr.ResetChecksum()
|
||||
}
|
||||
41
route_nat_gvisor.go
Normal file
41
route_nat_gvisor.go
Normal file
@@ -0,0 +1,41 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
func (w *NatWriter) RewritePacketBuffer(packetBuffer *stack.PacketBuffer) {
|
||||
var bindAddr tcpip.Address
|
||||
if packetBuffer.NetworkProtocolNumber == header.IPv4ProtocolNumber {
|
||||
bindAddr = tcpip.Address(w.inet4Address.AsSlice())
|
||||
} else {
|
||||
bindAddr = tcpip.Address(w.inet6Address.AsSlice())
|
||||
}
|
||||
var ipHdr header.Network
|
||||
switch packetBuffer.NetworkProtocolNumber {
|
||||
case header.IPv4ProtocolNumber:
|
||||
ipHdr = header.IPv4(packetBuffer.NetworkHeader().Slice())
|
||||
case header.IPv6ProtocolNumber:
|
||||
ipHdr = header.IPv6(packetBuffer.NetworkHeader().Slice())
|
||||
default:
|
||||
return
|
||||
}
|
||||
oldAddr := ipHdr.SourceAddress()
|
||||
if checksumHdr, needChecksum := ipHdr.(header.ChecksummableNetwork); needChecksum {
|
||||
checksumHdr.SetSourceAddressWithChecksumUpdate(bindAddr)
|
||||
} else {
|
||||
ipHdr.SetSourceAddress(bindAddr)
|
||||
}
|
||||
switch packetBuffer.TransportProtocolNumber {
|
||||
case header.TCPProtocolNumber:
|
||||
tcpHdr := header.TCP(packetBuffer.TransportHeader().Slice())
|
||||
tcpHdr.UpdateChecksumPseudoHeaderAddress(oldAddr, bindAddr, true)
|
||||
case header.UDPProtocolNumber:
|
||||
udpHdr := header.UDP(packetBuffer.TransportHeader().Slice())
|
||||
udpHdr.UpdateChecksumPseudoHeaderAddress(oldAddr, bindAddr, true)
|
||||
}
|
||||
}
|
||||
11
route_non_gvisor.go
Normal file
11
route_non_gvisor.go
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !with_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import "github.com/sagernet/sing/common/buf"
|
||||
|
||||
type DirectDestination interface {
|
||||
WritePacket(buffer *buf.Buffer) error
|
||||
Close() error
|
||||
Timeout() bool
|
||||
}
|
||||
4
stack.go
4
stack.go
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
@@ -22,8 +23,11 @@ type StackOptions struct {
|
||||
Inet6Address []netip.Prefix
|
||||
EndpointIndependentNat bool
|
||||
UDPTimeout int64
|
||||
Router Router
|
||||
Handler Handler
|
||||
Logger logger.Logger
|
||||
ForwarderBindInterface bool
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
}
|
||||
|
||||
func NewStack(
|
||||
|
||||
241
system.go
241
system.go
@@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/clashtcpip"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -19,7 +21,9 @@ import (
|
||||
type System struct {
|
||||
ctx context.Context
|
||||
tun Tun
|
||||
tunName string
|
||||
mtu uint32
|
||||
router Router
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
inet4Prefixes []netip.Prefix
|
||||
@@ -35,6 +39,9 @@ type System struct {
|
||||
tcpPort6 uint16
|
||||
tcpNat *TCPNat
|
||||
udpNat *udpnat.Service[netip.AddrPort]
|
||||
routeMapping *RouteMapping
|
||||
bindInterface bool
|
||||
interfaceFinder control.InterfaceFinder
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
@@ -46,14 +53,21 @@ type Session struct {
|
||||
|
||||
func NewSystem(options StackOptions) (Stack, error) {
|
||||
stack := &System{
|
||||
ctx: options.Context,
|
||||
tun: options.Tun,
|
||||
mtu: options.MTU,
|
||||
udpTimeout: options.UDPTimeout,
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
inet4Prefixes: options.Inet4Address,
|
||||
inet6Prefixes: options.Inet6Address,
|
||||
ctx: options.Context,
|
||||
tun: options.Tun,
|
||||
tunName: options.Name,
|
||||
mtu: options.MTU,
|
||||
udpTimeout: options.UDPTimeout,
|
||||
router: options.Router,
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
inet4Prefixes: options.Inet4Address,
|
||||
inet6Prefixes: options.Inet6Address,
|
||||
bindInterface: options.ForwarderBindInterface,
|
||||
interfaceFinder: options.InterfaceFinder,
|
||||
}
|
||||
if stack.router != nil {
|
||||
stack.routeMapping = NewRouteMapping(options.UDPTimeout)
|
||||
}
|
||||
if len(options.Inet4Address) > 0 {
|
||||
if options.Inet4Address[0].Bits() == 32 {
|
||||
@@ -83,8 +97,18 @@ func (s *System) Close() error {
|
||||
}
|
||||
|
||||
func (s *System) Start() error {
|
||||
var listener net.ListenConfig
|
||||
if s.bindInterface {
|
||||
listener.Control = control.Append(listener.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
err := control.BindToInterface(s.interfaceFinder, s.tunName, -1)(network, address, conn)
|
||||
if err != nil {
|
||||
s.logger.Warn("bind forwarder to interface: ", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if s.inet4Address.IsValid() {
|
||||
tcpListener, err := net.Listen("tcp4", net.JoinHostPort(s.inet4ServerAddress.String(), "0"))
|
||||
tcpListener, err := listener.Listen(s.ctx, "tcp4", net.JoinHostPort(s.inet4ServerAddress.String(), "0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,7 +117,7 @@ func (s *System) Start() error {
|
||||
go s.acceptLoop(tcpListener)
|
||||
}
|
||||
if s.inet6Address.IsValid() {
|
||||
tcpListener, err := net.Listen("tcp6", net.JoinHostPort(s.inet6ServerAddress.String(), "0"))
|
||||
tcpListener, err := listener.Listen(s.ctx, "tcp6", net.JoinHostPort(s.inet6ServerAddress.String(), "0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,7 +125,7 @@ func (s *System) Start() error {
|
||||
s.tcpPort6 = M.SocksaddrFromNet(tcpListener.Addr()).Port
|
||||
go s.acceptLoop(tcpListener)
|
||||
}
|
||||
s.tcpNat = NewNat()
|
||||
s.tcpNat = NewNat(s.ctx, time.Second*time.Duration(s.udpTimeout))
|
||||
s.udpNat = udpnat.New[netip.AddrPort](s.udpTimeout, s.handler)
|
||||
go s.tunLoop()
|
||||
return nil
|
||||
@@ -194,13 +218,14 @@ func (s *System) acceptLoop(listener net.Listener) {
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
s.handler.NewConnection(context.Background(), conn, M.Metadata{
|
||||
_ = s.handler.NewConnection(s.ctx, conn, M.Metadata{
|
||||
Source: M.SocksaddrFromNetIP(session.Source),
|
||||
Destination: destination,
|
||||
})
|
||||
conn.Close()
|
||||
time.Sleep(time.Second)
|
||||
s.tcpNat.Revoke(connPort, session)
|
||||
if tcpConn, isTCPConn := conn.(*net.TCPConn); isTCPConn {
|
||||
_ = tcpConn.SetLinger(0)
|
||||
}
|
||||
_ = conn.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -246,6 +271,21 @@ func (s *System) processIPv4TCP(packet clashtcpip.IPv4Packet, header clashtcpip.
|
||||
packet.SetDestinationIP(session.Source.Addr())
|
||||
header.SetDestinationPort(session.Source.Port())
|
||||
} else {
|
||||
if s.router != nil {
|
||||
session := RouteSession{4, syscall.IPPROTO_TCP, source, destination}
|
||||
action := s.routeMapping.Lookup(session, func() RouteAction {
|
||||
return s.router.RouteConnection(session, &systemTCPDirectPacketWriter4{s.tun, source})
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send ICMP unreachable
|
||||
return nil
|
||||
case *ActionDirect:
|
||||
return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error {
|
||||
return E.Cause(err, "route ipv4 tcp packet")
|
||||
})
|
||||
}
|
||||
}
|
||||
natPort := s.tcpNat.Lookup(source, destination)
|
||||
packet.SetSourceIP(s.inet4Address)
|
||||
header.SetSourcePort(natPort)
|
||||
@@ -272,6 +312,21 @@ func (s *System) processIPv6TCP(packet clashtcpip.IPv6Packet, header clashtcpip.
|
||||
packet.SetDestinationIP(session.Source.Addr())
|
||||
header.SetDestinationPort(session.Source.Port())
|
||||
} else {
|
||||
if s.router != nil {
|
||||
session := RouteSession{6, syscall.IPPROTO_TCP, source, destination}
|
||||
action := s.routeMapping.Lookup(session, func() RouteAction {
|
||||
return s.router.RouteConnection(session, &systemTCPDirectPacketWriter6{s.tun, source})
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send RST
|
||||
return nil
|
||||
case *ActionDirect:
|
||||
return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error {
|
||||
return E.Cause(err, "route ipv6 tcp packet")
|
||||
})
|
||||
}
|
||||
}
|
||||
natPort := s.tcpNat.Lookup(source, destination)
|
||||
packet.SetSourceIP(s.inet6Address)
|
||||
header.SetSourcePort(natPort)
|
||||
@@ -295,6 +350,21 @@ func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip.
|
||||
if !destination.Addr().IsGlobalUnicast() {
|
||||
return common.Error(s.tun.Write(packet))
|
||||
}
|
||||
if s.router != nil {
|
||||
routeSession := RouteSession{4, syscall.IPPROTO_UDP, source, destination}
|
||||
action := s.routeMapping.Lookup(routeSession, func() RouteAction {
|
||||
return s.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter4{s.tun, source})
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send icmp unreachable
|
||||
return nil
|
||||
case *ActionDirect:
|
||||
return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error {
|
||||
return E.Cause(err, "route ipv4 udp packet")
|
||||
})
|
||||
}
|
||||
}
|
||||
data := buf.As(header.Payload())
|
||||
if data.Len() == 0 {
|
||||
return nil
|
||||
@@ -307,7 +377,7 @@ func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip.
|
||||
headerLen := packet.HeaderLen() + clashtcpip.UDPHeaderSize
|
||||
headerCopy := make([]byte, headerLen)
|
||||
copy(headerCopy, packet[:headerLen])
|
||||
return &systemPacketWriter4{s.tun, headerCopy, source}
|
||||
return &systemUDPPacketWriter4{s.tun, headerCopy, source}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -318,6 +388,21 @@ func (s *System) processIPv6UDP(packet clashtcpip.IPv6Packet, header clashtcpip.
|
||||
if !destination.Addr().IsGlobalUnicast() {
|
||||
return common.Error(s.tun.Write(packet))
|
||||
}
|
||||
if s.router != nil {
|
||||
routeSession := RouteSession{6, syscall.IPPROTO_UDP, source, destination}
|
||||
action := s.routeMapping.Lookup(routeSession, func() RouteAction {
|
||||
return s.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter6{s.tun, source})
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send icmp unreachable
|
||||
return nil
|
||||
case *ActionDirect:
|
||||
return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error {
|
||||
return E.Cause(err, "route ipv6 udp packet")
|
||||
})
|
||||
}
|
||||
}
|
||||
data := buf.As(header.Payload())
|
||||
if data.Len() == 0 {
|
||||
return nil
|
||||
@@ -330,12 +415,27 @@ func (s *System) processIPv6UDP(packet clashtcpip.IPv6Packet, header clashtcpip.
|
||||
headerLen := len(packet) - int(header.Length()) + clashtcpip.UDPHeaderSize
|
||||
headerCopy := make([]byte, headerLen)
|
||||
copy(headerCopy, packet[:headerLen])
|
||||
return &systemPacketWriter6{s.tun, headerCopy, source}
|
||||
return &systemUDPPacketWriter6{s.tun, headerCopy, source}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *System) processIPv4ICMP(packet clashtcpip.IPv4Packet, header clashtcpip.ICMPPacket) error {
|
||||
if s.router != nil {
|
||||
routeSession := RouteSession{4, clashtcpip.ICMP, netip.AddrPortFrom(packet.SourceIP(), 0), netip.AddrPortFrom(packet.DestinationIP(), 0)}
|
||||
action := s.routeMapping.Lookup(routeSession, func() RouteAction {
|
||||
return s.router.RouteConnection(routeSession, &systemICMPDirectPacketWriter4{s.tun, packet.SourceIP()})
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send icmp unreachable
|
||||
return nil
|
||||
case *ActionDirect:
|
||||
return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error {
|
||||
return E.Cause(err, "route ipv4 icmp packet")
|
||||
})
|
||||
}
|
||||
}
|
||||
if header.Type() != clashtcpip.ICMPTypePingRequest || header.Code() != 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -349,6 +449,21 @@ func (s *System) processIPv4ICMP(packet clashtcpip.IPv4Packet, header clashtcpip
|
||||
}
|
||||
|
||||
func (s *System) processIPv6ICMP(packet clashtcpip.IPv6Packet, header clashtcpip.ICMPv6Packet) error {
|
||||
if s.router != nil {
|
||||
routeSession := RouteSession{6, clashtcpip.ICMPv6, netip.AddrPortFrom(packet.SourceIP(), 0), netip.AddrPortFrom(packet.DestinationIP(), 0)}
|
||||
action := s.routeMapping.Lookup(routeSession, func() RouteAction {
|
||||
return s.router.RouteConnection(routeSession, &systemICMPDirectPacketWriter6{s.tun, packet.SourceIP()})
|
||||
})
|
||||
switch actionType := action.(type) {
|
||||
case *ActionBlock:
|
||||
// TODO: send icmp unreachable
|
||||
return nil
|
||||
case *ActionDirect:
|
||||
return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error {
|
||||
return E.Cause(err, "route ipv6 icmp packet")
|
||||
})
|
||||
}
|
||||
}
|
||||
if header.Type() != clashtcpip.ICMPv6EchoRequest || header.Code() != 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -361,13 +476,97 @@ func (s *System) processIPv6ICMP(packet clashtcpip.IPv6Packet, header clashtcpip
|
||||
return common.Error(s.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemPacketWriter4 struct {
|
||||
type systemTCPDirectPacketWriter4 struct {
|
||||
tun Tun
|
||||
source netip.AddrPort
|
||||
}
|
||||
|
||||
func (w *systemTCPDirectPacketWriter4) WritePacket(p []byte) error {
|
||||
packet := clashtcpip.IPv4Packet(p)
|
||||
header := clashtcpip.TCPPacket(packet.Payload())
|
||||
packet.SetDestinationIP(w.source.Addr())
|
||||
header.SetDestinationPort(w.source.Port())
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(w.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemTCPDirectPacketWriter6 struct {
|
||||
tun Tun
|
||||
source netip.AddrPort
|
||||
}
|
||||
|
||||
func (w *systemTCPDirectPacketWriter6) WritePacket(p []byte) error {
|
||||
packet := clashtcpip.IPv6Packet(p)
|
||||
header := clashtcpip.TCPPacket(packet.Payload())
|
||||
packet.SetDestinationIP(w.source.Addr())
|
||||
header.SetDestinationPort(w.source.Port())
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(w.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemUDPDirectPacketWriter4 struct {
|
||||
tun Tun
|
||||
source netip.AddrPort
|
||||
}
|
||||
|
||||
func (w *systemUDPDirectPacketWriter4) WritePacket(p []byte) error {
|
||||
packet := clashtcpip.IPv4Packet(p)
|
||||
header := clashtcpip.UDPPacket(packet.Payload())
|
||||
packet.SetDestinationIP(w.source.Addr())
|
||||
header.SetDestinationPort(w.source.Port())
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(w.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemUDPDirectPacketWriter6 struct {
|
||||
tun Tun
|
||||
source netip.AddrPort
|
||||
}
|
||||
|
||||
func (w *systemUDPDirectPacketWriter6) WritePacket(p []byte) error {
|
||||
packet := clashtcpip.IPv6Packet(p)
|
||||
header := clashtcpip.UDPPacket(packet.Payload())
|
||||
packet.SetDestinationIP(w.source.Addr())
|
||||
header.SetDestinationPort(w.source.Port())
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(w.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemICMPDirectPacketWriter4 struct {
|
||||
tun Tun
|
||||
source netip.Addr
|
||||
}
|
||||
|
||||
func (w *systemICMPDirectPacketWriter4) WritePacket(p []byte) error {
|
||||
packet := clashtcpip.IPv4Packet(p)
|
||||
packet.SetDestinationIP(w.source)
|
||||
packet.ResetChecksum()
|
||||
return common.Error(w.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemICMPDirectPacketWriter6 struct {
|
||||
tun Tun
|
||||
source netip.Addr
|
||||
}
|
||||
|
||||
func (w *systemICMPDirectPacketWriter6) WritePacket(p []byte) error {
|
||||
packet := clashtcpip.IPv6Packet(p)
|
||||
packet.SetDestinationIP(w.source)
|
||||
packet.ResetChecksum()
|
||||
return common.Error(w.tun.Write(packet))
|
||||
}
|
||||
|
||||
type systemUDPPacketWriter4 struct {
|
||||
tun Tun
|
||||
header []byte
|
||||
source netip.AddrPort
|
||||
}
|
||||
|
||||
func (w *systemPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
func (w *systemUDPPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
newPacket := buf.StackNewSize(len(w.header) + buffer.Len())
|
||||
defer newPacket.Release()
|
||||
newPacket.Write(w.header)
|
||||
@@ -385,13 +584,13 @@ func (w *systemPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Sock
|
||||
return common.Error(w.tun.Write(newPacket.Bytes()))
|
||||
}
|
||||
|
||||
type systemPacketWriter6 struct {
|
||||
type systemUDPPacketWriter6 struct {
|
||||
tun Tun
|
||||
header []byte
|
||||
source netip.AddrPort
|
||||
}
|
||||
|
||||
func (w *systemPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
func (w *systemUDPPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
newPacket := buf.StackNewSize(len(w.header) + buffer.Len())
|
||||
defer newPacket.Release()
|
||||
newPacket.Write(w.header)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TCPNat struct {
|
||||
@@ -16,20 +18,54 @@ type TCPNat struct {
|
||||
type TCPSession struct {
|
||||
Source netip.AddrPort
|
||||
Destination netip.AddrPort
|
||||
LastActive time.Time
|
||||
}
|
||||
|
||||
func NewNat() *TCPNat {
|
||||
return &TCPNat{
|
||||
func NewNat(ctx context.Context, timeout time.Duration) *TCPNat {
|
||||
natMap := &TCPNat{
|
||||
portIndex: 10000,
|
||||
addrMap: make(map[netip.AddrPort]uint16),
|
||||
portMap: make(map[uint16]*TCPSession),
|
||||
}
|
||||
go natMap.loopCheckTimeout(ctx, timeout)
|
||||
return natMap
|
||||
}
|
||||
|
||||
func (n *TCPNat) loopCheckTimeout(ctx context.Context, timeout time.Duration) {
|
||||
ticker := time.NewTicker(timeout)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
n.checkTimeout(timeout)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *TCPNat) checkTimeout(timeout time.Duration) {
|
||||
now := time.Now()
|
||||
n.portAccess.Lock()
|
||||
defer n.portAccess.Unlock()
|
||||
n.addrAccess.Lock()
|
||||
defer n.addrAccess.Unlock()
|
||||
for natPort, session := range n.portMap {
|
||||
if now.Sub(session.LastActive) > timeout {
|
||||
delete(n.addrMap, session.Source)
|
||||
delete(n.portMap, natPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *TCPNat) LookupBack(port uint16) *TCPSession {
|
||||
n.portAccess.RLock()
|
||||
defer n.portAccess.RUnlock()
|
||||
return n.portMap[port]
|
||||
session := n.portMap[port]
|
||||
n.portAccess.RUnlock()
|
||||
if session != nil {
|
||||
session.LastActive = time.Now()
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
func (n *TCPNat) Lookup(source netip.AddrPort, destination netip.AddrPort) uint16 {
|
||||
@@ -53,16 +89,8 @@ func (n *TCPNat) Lookup(source netip.AddrPort, destination netip.AddrPort) uint1
|
||||
n.portMap[nextPort] = &TCPSession{
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
LastActive: time.Now(),
|
||||
}
|
||||
n.portAccess.Unlock()
|
||||
return nextPort
|
||||
}
|
||||
|
||||
func (n *TCPNat) Revoke(natPort uint16, session *TCPSession) {
|
||||
n.addrAccess.Lock()
|
||||
delete(n.addrMap, session.Source)
|
||||
n.addrAccess.Unlock()
|
||||
n.portAccess.Lock()
|
||||
delete(n.portMap, natPort)
|
||||
n.portAccess.Unlock()
|
||||
}
|
||||
|
||||
14
timeout.go
14
timeout.go
@@ -1,14 +0,0 @@
|
||||
package tun
|
||||
|
||||
import "context"
|
||||
|
||||
type needTimeoutKey struct{}
|
||||
|
||||
func ContextWithNeedTimeout(ctx context.Context, need bool) context.Context {
|
||||
return context.WithValue(ctx, (*needTimeoutKey)(nil), need)
|
||||
}
|
||||
|
||||
func NeedTimeoutFromContext(ctx context.Context) bool {
|
||||
need, _ := ctx.Value((*needTimeoutKey)(nil)).(bool)
|
||||
return need
|
||||
}
|
||||
1
tun.go
1
tun.go
@@ -46,6 +46,7 @@ type Options struct {
|
||||
ExcludePackage []string
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
TableIndex int
|
||||
FileDescriptor int
|
||||
}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -28,23 +29,29 @@ type NativeTun struct {
|
||||
inet6Address string
|
||||
}
|
||||
|
||||
func Open(options Options) (Tun, error) {
|
||||
ifIndex := -1
|
||||
_, err := fmt.Sscanf(options.Name, "utun%d", &ifIndex)
|
||||
if err != nil {
|
||||
return nil, E.New("bad tun name: ", options.Name)
|
||||
func New(options Options) (Tun, error) {
|
||||
var tunFd int
|
||||
if options.FileDescriptor == 0 {
|
||||
ifIndex := -1
|
||||
_, err := fmt.Sscanf(options.Name, "utun%d", &ifIndex)
|
||||
if err != nil {
|
||||
return nil, E.New("bad tun name: ", options.Name)
|
||||
}
|
||||
|
||||
tunFd, err = unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = configure(tunFd, ifIndex, options.Name, options)
|
||||
if err != nil {
|
||||
unix.Close(tunFd)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
tunFd = options.FileDescriptor
|
||||
}
|
||||
|
||||
tunFd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = configure(tunFd, ifIndex, options.Name, options)
|
||||
if err != nil {
|
||||
unix.Close(tunFd)
|
||||
return nil, err
|
||||
}
|
||||
nativeTun := &NativeTun{
|
||||
tunFile: os.NewFile(uintptr(tunFd), "utun"),
|
||||
mtu: options.MTU,
|
||||
@@ -95,6 +102,7 @@ func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (t *NativeTun) Close() error {
|
||||
flushDNSCache()
|
||||
return t.tunFile.Close()
|
||||
}
|
||||
|
||||
@@ -279,6 +287,7 @@ func configure(tunFd int, ifIndex int, name string, options Options) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
flushDNSCache()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -320,3 +329,7 @@ func addRoute(destination netip.Prefix, gateway netip.Addr) error {
|
||||
return common.Error(unix.Write(socketFd, request))
|
||||
})
|
||||
}
|
||||
|
||||
func flushDNSCache() {
|
||||
shell.Exec("dscacheutil", "-flushcache").Start()
|
||||
}
|
||||
|
||||
71
tun_linux.go
71
tun_linux.go
@@ -5,13 +5,16 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/netlink"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -25,26 +28,36 @@ type NativeTun struct {
|
||||
ruleIndex6 []int
|
||||
}
|
||||
|
||||
func Open(options Options) (Tun, error) {
|
||||
tunFd, err := open(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func New(options Options) (Tun, error) {
|
||||
if options.FileDescriptor == 0 {
|
||||
tunFd, err := open(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tunLink, err := netlink.LinkByName(options.Name)
|
||||
if err != nil {
|
||||
return nil, E.Errors(err, unix.Close(tunFd))
|
||||
}
|
||||
nativeTun := &NativeTun{
|
||||
tunFd: tunFd,
|
||||
tunFile: os.NewFile(uintptr(tunFd), "tun"),
|
||||
options: options,
|
||||
}
|
||||
runtime.SetFinalizer(nativeTun.tunFile, nil)
|
||||
err = nativeTun.configure(tunLink)
|
||||
if err != nil {
|
||||
return nil, E.Errors(err, unix.Close(tunFd))
|
||||
}
|
||||
return nativeTun, nil
|
||||
} else {
|
||||
nativeTun := &NativeTun{
|
||||
tunFd: options.FileDescriptor,
|
||||
tunFile: os.NewFile(uintptr(options.FileDescriptor), "tun"),
|
||||
options: options,
|
||||
}
|
||||
runtime.SetFinalizer(nativeTun.tunFile, nil)
|
||||
return nativeTun, nil
|
||||
}
|
||||
tunLink, err := netlink.LinkByName(options.Name)
|
||||
if err != nil {
|
||||
return nil, E.Errors(err, unix.Close(tunFd))
|
||||
}
|
||||
nativeTun := &NativeTun{
|
||||
tunFd: tunFd,
|
||||
tunFile: os.NewFile(uintptr(tunFd), "tun"),
|
||||
options: options,
|
||||
}
|
||||
runtime.SetFinalizer(nativeTun.tunFile, nil)
|
||||
err = nativeTun.configure(tunLink)
|
||||
if err != nil {
|
||||
return nil, E.Errors(err, unix.Close(tunFd))
|
||||
}
|
||||
return nativeTun, nil
|
||||
}
|
||||
|
||||
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
||||
@@ -154,6 +167,8 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
|
||||
return err
|
||||
}
|
||||
|
||||
setSearchDomainForSystemdResolved(t.options.Name)
|
||||
|
||||
if t.options.AutoRoute && runtime.GOOS == "android" {
|
||||
t.interfaceCallback = t.options.InterfaceMonitor.RegisterCallback(t.routeUpdate)
|
||||
}
|
||||
@@ -373,7 +388,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IPProto = unix.IPPROTO_ICMP
|
||||
it.IPProto = syscall.IPPROTO_ICMP
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
@@ -382,7 +397,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IPProto = unix.IPPROTO_ICMPV6
|
||||
it.IPProto = syscall.IPPROTO_ICMPV6
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
@@ -515,6 +530,9 @@ func (t *NativeTun) setRules() error {
|
||||
}
|
||||
|
||||
func (t *NativeTun) unsetRoute() error {
|
||||
if t.options.FileDescriptor > 0 {
|
||||
return nil
|
||||
}
|
||||
tunLink, err := netlink.LinkByName(t.options.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -530,6 +548,9 @@ func (t *NativeTun) unsetRoute0(tunLink netlink.Link) error {
|
||||
}
|
||||
|
||||
func (t *NativeTun) unsetRules() error {
|
||||
if t.options.FileDescriptor > 0 {
|
||||
return nil
|
||||
}
|
||||
if len(t.ruleIndex6) > 0 {
|
||||
for _, index := range t.ruleIndex6 {
|
||||
ruleToDel := netlink.NewRule()
|
||||
@@ -577,3 +598,11 @@ func (t *NativeTun) routeUpdate(event int) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSearchDomainForSystemdResolved(interfaceName string) {
|
||||
ctlPath, err := exec.LookPath("resolvectl")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
shell.Exec(ctlPath, "domain", interfaceName, "~.").Run()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func Open(config Options) (Tun, error) {
|
||||
func New(config Options) (Tun, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ type NativeTun struct {
|
||||
fwpmSession uintptr
|
||||
}
|
||||
|
||||
func Open(options Options) (WinTun, error) {
|
||||
func New(options Options) (WinTun, error) {
|
||||
if options.FileDescriptor != 0 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
adapter, err := wintun.CreateAdapter(options.Name, TunnelType, generateGUIDByDeviceName(options.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -113,6 +116,10 @@ func (t *NativeTun) configure() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
err := windnsapi.FlushResolverCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet4Address) > 0 {
|
||||
inetIf, err := luid.IPInterface(winipcfg.AddressFamily(windows.AF_INET))
|
||||
@@ -328,11 +335,6 @@ func (t *NativeTun) configure() error {
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
|
||||
err = windnsapi.FlushResolverCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -476,7 +478,9 @@ func (t *NativeTun) Close() error {
|
||||
if t.fwpmSession != 0 {
|
||||
winsys.FwpmEngineClose0(t.fwpmSession)
|
||||
}
|
||||
windnsapi.FlushResolverCache()
|
||||
if t.options.AutoRoute {
|
||||
windnsapi.FlushResolverCache()
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user