Compare commits

..

20 Commits

Author SHA1 Message Date
世界
a6a8c23d65 Update dependencies 2023-04-21 19:37:15 +08:00
世界
8bf4113343 Ignore system bind error 2023-04-20 11:56:16 +08:00
世界
9c6e70b7cc Revert LRU cache changes 2023-04-19 23:48:14 +08:00
世界
d744d03d93 Fix default interface check for darwin 2023-04-19 14:16:14 +08:00
世界
53f50347e0 Fix system nat mapping 2023-04-19 14:16:14 +08:00
世界
bf7110b1ab Update udpnat usage 2023-04-18 10:53:39 +08:00
世界
d880656b52 Fix system stack for ios 2023-04-17 20:02:10 +08:00
世界
499c0aed67 Fix monitor for ios 2023-04-17 19:23:13 +08:00
世界
8848c0e4cb Set search domain for systemd-resolved 2023-03-26 16:09:54 +08:00
世界
980d4bf9a3 Flush DNS cache on macOS 2023-03-26 10:37:44 +08:00
世界
ca53ccf346 Fix action type 2023-03-24 16:22:20 +08:00
世界
f0c27d037c Update dependencies 2023-03-24 07:12:16 +08:00
世界
35d565af65 Fix icmp constant 2023-03-23 15:33:25 +08:00
世界
45b089e6bb Fix gVisor udp conn timeout 2023-03-23 15:13:20 +08:00
世界
56bedd2f05 Add route support 2023-03-22 01:28:18 +08:00
世界
fe89bbded2 Create gVisor stack by default in NE 2023-03-15 21:47:16 +08:00
世界
839f1792e4 Fix close platform tun 2023-03-13 19:36:43 +08:00
世界
20226b91c9 Fix system stack not passing context to handler 2023-03-12 21:53:45 +08:00
世界
8507bb3a0a Fix windows monitor 2023-03-09 15:51:41 +08:00
世界
0cdb0eed74 Add create tun from file descriptor 2023-02-26 17:11:24 +08:00
23 changed files with 926 additions and 134 deletions

View File

@@ -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 .

View File

@@ -2,7 +2,7 @@
Simple transparent proxy library.
For Linux, Windows and macOS.
For Linux, Windows, macOS and iOS.
## License

6
go.mod
View File

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

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

@@ -46,6 +46,7 @@ type Options struct {
ExcludePackage []string
InterfaceMonitor DefaultInterfaceMonitor
TableIndex int
FileDescriptor int
}
func CalculateInterfaceName(name string) (tunName string) {

View File

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

View File

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

View File

@@ -6,6 +6,6 @@ import (
"os"
)
func Open(config Options) (Tun, error) {
func New(config Options) (Tun, error) {
return nil, os.ErrInvalid
}

View File

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