From c21c623174132c4153a795fc07ec232110628242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 28 Nov 2024 14:27:11 +0800 Subject: [PATCH] Add Tun.Name --- go.mod | 2 +- go.sum | 4 +- tun.go | 1 + tun_darwin.go | 8 + tun_linux.go | 439 +++++++++++++++++++++++++------------------------ tun_windows.go | 4 + 6 files changed, 241 insertions(+), 217 deletions(-) diff --git a/go.mod b/go.mod index ba0bcfa..0bb96e7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/nftables v0.3.0-beta.4 - github.com/sagernet/sing v0.6.0-alpha.25 + github.com/sagernet/sing v0.6.0-beta.2 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/net v0.26.0 diff --git a/go.sum b/go.sum index 8b45e30..e2ea32a 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/sing v0.6.0-alpha.25 h1:r/UxU+1O6436MjvEXEMRfBBtqMEImgA6uqxezXKZ/Rs= -github.com/sagernet/sing v0.6.0-alpha.25/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.0-beta.2 h1:Dcutp3kxrsZes9q3oTiHQhYYjQvDn5rwp1OI9fDLYwQ= +github.com/sagernet/sing v0.6.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= diff --git a/tun.go b/tun.go index 4664cef..a2cae52 100644 --- a/tun.go +++ b/tun.go @@ -25,6 +25,7 @@ type Handler interface { type Tun interface { io.ReadWriter N.VectorisedWriter + Name() (string, error) Start() error Close() error } diff --git a/tun_darwin.go b/tun_darwin.go index b7f8aa7..52872fa 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -32,6 +32,14 @@ type NativeTun struct { routerSet bool } +func (t *NativeTun) Name() (string, error) { + return unix.GetsockoptString( + int(t.tunFile.Fd()), + 2, /* #define SYSPROTO_CONTROL 2 */ + 2, /* #define UTUN_OPT_IFNAME 2 */ + ) +} + func New(options Options) (Tun, error) { var tunFd int if options.FileDescriptor == 0 { diff --git a/tun_linux.go b/tun_linux.go index 9336ecd..ea0cef6 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -85,204 +85,6 @@ func New(options Options) (Tun, error) { return nativeTun, nil } -func (t *NativeTun) FrontHeadroom() int { - if t.vnetHdr { - return virtioNetHdrLen - } - return 0 -} - -func (t *NativeTun) Read(p []byte) (n int, err error) { - if t.vnetHdr { - n, err = t.tunFile.Read(t.writeBuffer) - if err != nil { - if errors.Is(err, syscall.EBADFD) { - err = os.ErrClosed - } - return - } - var sizes [1]int - n, err = handleVirtioRead(t.writeBuffer[:n], [][]byte{p}, sizes[:], 0) - if err != nil { - return - } - if n == 0 { - return - } - n = sizes[0] - return - } else { - return t.tunFile.Read(p) - } -} - -// handleVirtioRead splits in into bufs, leaving offset bytes at the front of -// each buffer. It mutates sizes to reflect the size of each element of bufs, -// and returns the number of packets read. -func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, error) { - var hdr virtioNetHdr - err := hdr.decode(in) - if err != nil { - return 0, err - } - in = in[virtioNetHdrLen:] - - options, err := hdr.toGSOOptions() - if err != nil { - return 0, err - } - - // Don't trust HdrLen from the kernel as it can be equal to the length - // of the entire first packet when the kernel is handling it as part of a - // FORWARD path. Instead, parse the transport header length and add it onto - // CsumStart, which is synonymous for IP header length. - if options.GSOType == GSOUDPL4 { - options.HdrLen = options.CsumStart + 8 - } else if options.GSOType != GSONone { - if len(in) <= int(options.CsumStart+12) { - return 0, errors.New("packet is too short") - } - - tcpHLen := uint16(in[options.CsumStart+12] >> 4 * 4) - if tcpHLen < 20 || tcpHLen > 60 { - // A TCP header must be between 20 and 60 bytes in length. - return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen) - } - options.HdrLen = options.CsumStart + tcpHLen - } - - return GSOSplit(in, options, bufs, sizes, offset) -} - -func (t *NativeTun) Write(p []byte) (n int, err error) { - if t.vnetHdr { - buffer := buf.Get(virtioNetHdrLen + len(p)) - copy(buffer[virtioNetHdrLen:], p) - _, err = t.BatchWrite([][]byte{buffer}, virtioNetHdrLen) - buf.Put(buffer) - if err != nil { - return - } - n = len(p) - return - } - return t.tunFile.Write(p) -} - -func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error { - if t.vnetHdr { - n := buf.LenMulti(buffers) - buffer := buf.NewSize(virtioNetHdrLen + n) - buffer.Truncate(virtioNetHdrLen) - buf.CopyMulti(buffer.Extend(n), buffers) - _, err := t.tunFile.Write(buffer.Bytes()) - buffer.Release() - return err - } else { - return t.tunWriter.WriteVectorised(buffers) - } -} - -func (t *NativeTun) BatchSize() int { - if !t.vnetHdr { - return 1 - } - /* // Not works on some devices: https://github.com/SagerNet/sing-box/issues/1605 - batchSize := int(gsoMaxSize/t.options.MTU) * 2 - if batchSize > idealBatchSize { - batchSize = idealBatchSize - } - return batchSize*/ - return idealBatchSize -} - -func (t *NativeTun) probeTCPGRO() error { - ipPort := netip.AddrPortFrom(t.options.Inet4Address[0].Addr(), 0) - fingerprint := []byte("sing-tun-probe-tun-gro") - segmentSize := len(fingerprint) - iphLen := 20 - tcphLen := 20 - totalLen := iphLen + tcphLen + segmentSize - bufs := make([][]byte, 2) - for i := range bufs { - bufs[i] = make([]byte, virtioNetHdrLen+totalLen, virtioNetHdrLen+(totalLen*2)) - ipv4H := header.IPv4(bufs[i][virtioNetHdrLen:]) - ipv4H.Encode(&header.IPv4Fields{ - SrcAddr: ipPort.Addr(), - DstAddr: ipPort.Addr(), - Protocol: unix.IPPROTO_TCP, - // Use a zero value TTL as best effort means to reduce chance of - // probe packet leaking further than it needs to. - TTL: 0, - TotalLength: uint16(totalLen), - }) - tcpH := header.TCP(bufs[i][virtioNetHdrLen+iphLen:]) - tcpH.Encode(&header.TCPFields{ - SrcPort: ipPort.Port(), - DstPort: ipPort.Port(), - SeqNum: 1 + uint32(i*segmentSize), - AckNum: 1, - DataOffset: 20, - Flags: header.TCPFlagAck, - WindowSize: 3000, - }) - copy(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], fingerprint) - ipv4H.SetChecksum(^ipv4H.CalculateChecksum()) - pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddressSlice(), ipv4H.DestinationAddressSlice(), uint16(tcphLen+segmentSize)) - pseudoCsum = checksum.Checksum(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], pseudoCsum) - tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum)) - } - _, err := t.BatchWrite(bufs, virtioNetHdrLen) - return err -} - -func (t *NativeTun) BatchRead(buffers [][]byte, offset int, readN []int) (n int, err error) { - t.readAccess.Lock() - defer t.readAccess.Unlock() - n, err = t.tunFile.Read(t.writeBuffer) - if err != nil { - return - } - return handleVirtioRead(t.writeBuffer[:n], buffers, readN, offset) -} - -func (t *NativeTun) BatchWrite(buffers [][]byte, offset int) (int, error) { - t.writeAccess.Lock() - defer func() { - t.tcpGROTable.reset() - t.udpGROTable.reset() - t.writeAccess.Unlock() - }() - var ( - errs error - total int - ) - t.gsoToWrite = t.gsoToWrite[:0] - if t.vnetHdr { - err := handleGRO(buffers, offset, t.tcpGROTable, t.udpGROTable, t.gro, &t.gsoToWrite) - if err != nil { - return 0, err - } - offset -= virtioNetHdrLen - } else { - for i := range buffers { - t.gsoToWrite = append(t.gsoToWrite, i) - } - } - for _, toWrite := range t.gsoToWrite { - n, err := t.tunFile.Write(buffers[toWrite][offset:]) - if errors.Is(err, syscall.EBADFD) { - return total, os.ErrClosed - } - if err != nil { - errs = errors.Join(errs, err) - } else { - total += n - } - } - return total, errs -} - var controlPath string func init() { @@ -300,29 +102,26 @@ func open(name string, vnetHdr bool) (int, error) { if err != nil { return -1, err } - - var ifr struct { - name [16]byte - flags uint16 - _ [22]byte + ifr, err := unix.NewIfreq(name) + if err != nil { + unix.Close(fd) + return 0, err } - - copy(ifr.name[:], name) - ifr.flags = unix.IFF_TUN | unix.IFF_NO_PI + flags := unix.IFF_TUN | unix.IFF_NO_PI if vnetHdr { - ifr.flags |= unix.IFF_VNET_HDR + flags |= unix.IFF_VNET_HDR } - _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TUNSETIFF, uintptr(unsafe.Pointer(&ifr))) - if errno != 0 { + ifr.SetUint16(uint16(flags)) + err = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr) + if err != nil { unix.Close(fd) - return -1, errno + return 0, err } - - if err = unix.SetNonblock(fd, true); err != nil { + err = unix.SetNonblock(fd, true) + if err != nil { unix.Close(fd) - return -1, err + return 0, err } - return fd, nil } @@ -408,6 +207,60 @@ func (t *NativeTun) enableGSO() error { return nil } +func (t *NativeTun) probeTCPGRO() error { + ipPort := netip.AddrPortFrom(t.options.Inet4Address[0].Addr(), 0) + fingerprint := []byte("sing-tun-probe-tun-gro") + segmentSize := len(fingerprint) + iphLen := 20 + tcphLen := 20 + totalLen := iphLen + tcphLen + segmentSize + bufs := make([][]byte, 2) + for i := range bufs { + bufs[i] = make([]byte, virtioNetHdrLen+totalLen, virtioNetHdrLen+(totalLen*2)) + ipv4H := header.IPv4(bufs[i][virtioNetHdrLen:]) + ipv4H.Encode(&header.IPv4Fields{ + SrcAddr: ipPort.Addr(), + DstAddr: ipPort.Addr(), + Protocol: unix.IPPROTO_TCP, + // Use a zero value TTL as best effort means to reduce chance of + // probe packet leaking further than it needs to. + TTL: 0, + TotalLength: uint16(totalLen), + }) + tcpH := header.TCP(bufs[i][virtioNetHdrLen+iphLen:]) + tcpH.Encode(&header.TCPFields{ + SrcPort: ipPort.Port(), + DstPort: ipPort.Port(), + SeqNum: 1 + uint32(i*segmentSize), + AckNum: 1, + DataOffset: 20, + Flags: header.TCPFlagAck, + WindowSize: 3000, + }) + copy(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], fingerprint) + ipv4H.SetChecksum(^ipv4H.CalculateChecksum()) + pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddressSlice(), ipv4H.DestinationAddressSlice(), uint16(tcphLen+segmentSize)) + pseudoCsum = checksum.Checksum(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], pseudoCsum) + tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum)) + } + _, err := t.BatchWrite(bufs, virtioNetHdrLen) + return err +} + +func (t *NativeTun) Name() (string, error) { + var ifr [unix.IFNAMSIZ + 64]byte + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(t.tunFd), + uintptr(unix.TUNGETIFF), + uintptr(unsafe.Pointer(&ifr[0])), + ) + if errno != 0 { + return "", os.NewSyscallError("ioctl TUNGETIFF", errno) + } + return unix.ByteSliceToString(ifr[:]), nil +} + func (t *NativeTun) Start() error { if t.options.FileDescriptor != 0 { return nil @@ -473,6 +326,164 @@ func (t *NativeTun) Close() error { return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile))) } +func (t *NativeTun) Read(p []byte) (n int, err error) { + if t.vnetHdr { + n, err = t.tunFile.Read(t.writeBuffer) + if err != nil { + if errors.Is(err, syscall.EBADFD) { + err = os.ErrClosed + } + return + } + var sizes [1]int + n, err = handleVirtioRead(t.writeBuffer[:n], [][]byte{p}, sizes[:], 0) + if err != nil { + return + } + if n == 0 { + return + } + n = sizes[0] + return + } else { + return t.tunFile.Read(p) + } +} + +// handleVirtioRead splits in into bufs, leaving offset bytes at the front of +// each buffer. It mutates sizes to reflect the size of each element of bufs, +// and returns the number of packets read. +func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, error) { + var hdr virtioNetHdr + err := hdr.decode(in) + if err != nil { + return 0, err + } + in = in[virtioNetHdrLen:] + + options, err := hdr.toGSOOptions() + if err != nil { + return 0, err + } + + // Don't trust HdrLen from the kernel as it can be equal to the length + // of the entire first packet when the kernel is handling it as part of a + // FORWARD path. Instead, parse the transport header length and add it onto + // CsumStart, which is synonymous for IP header length. + if options.GSOType == GSOUDPL4 { + options.HdrLen = options.CsumStart + 8 + } else if options.GSOType != GSONone { + if len(in) <= int(options.CsumStart+12) { + return 0, errors.New("packet is too short") + } + + tcpHLen := uint16(in[options.CsumStart+12] >> 4 * 4) + if tcpHLen < 20 || tcpHLen > 60 { + // A TCP header must be between 20 and 60 bytes in length. + return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen) + } + options.HdrLen = options.CsumStart + tcpHLen + } + + return GSOSplit(in, options, bufs, sizes, offset) +} + +func (t *NativeTun) Write(p []byte) (n int, err error) { + if t.vnetHdr { + buffer := buf.Get(virtioNetHdrLen + len(p)) + copy(buffer[virtioNetHdrLen:], p) + _, err = t.BatchWrite([][]byte{buffer}, virtioNetHdrLen) + buf.Put(buffer) + if err != nil { + return + } + n = len(p) + return + } + return t.tunFile.Write(p) +} + +func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error { + if t.vnetHdr { + n := buf.LenMulti(buffers) + buffer := buf.NewSize(virtioNetHdrLen + n) + buffer.Truncate(virtioNetHdrLen) + buf.CopyMulti(buffer.Extend(n), buffers) + _, err := t.tunFile.Write(buffer.Bytes()) + buffer.Release() + return err + } else { + return t.tunWriter.WriteVectorised(buffers) + } +} + +func (t *NativeTun) FrontHeadroom() int { + if t.vnetHdr { + return virtioNetHdrLen + } + return 0 +} + +func (t *NativeTun) BatchSize() int { + if !t.vnetHdr { + return 1 + } + /* // Not works on some devices: https://github.com/SagerNet/sing-box/issues/1605 + batchSize := int(gsoMaxSize/t.options.MTU) * 2 + if batchSize > idealBatchSize { + batchSize = idealBatchSize + } + return batchSize*/ + return idealBatchSize +} + +func (t *NativeTun) BatchRead(buffers [][]byte, offset int, readN []int) (n int, err error) { + t.readAccess.Lock() + defer t.readAccess.Unlock() + n, err = t.tunFile.Read(t.writeBuffer) + if err != nil { + return + } + return handleVirtioRead(t.writeBuffer[:n], buffers, readN, offset) +} + +func (t *NativeTun) BatchWrite(buffers [][]byte, offset int) (int, error) { + t.writeAccess.Lock() + defer func() { + t.tcpGROTable.reset() + t.udpGROTable.reset() + t.writeAccess.Unlock() + }() + var ( + errs error + total int + ) + t.gsoToWrite = t.gsoToWrite[:0] + if t.vnetHdr { + err := handleGRO(buffers, offset, t.tcpGROTable, t.udpGROTable, t.gro, &t.gsoToWrite) + if err != nil { + return 0, err + } + offset -= virtioNetHdrLen + } else { + for i := range buffers { + t.gsoToWrite = append(t.gsoToWrite, i) + } + } + for _, toWrite := range t.gsoToWrite { + n, err := t.tunFile.Write(buffers[toWrite][offset:]) + if errors.Is(err, syscall.EBADFD) { + return total, os.ErrClosed + } + if err != nil { + errs = errors.Join(errs, err) + } else { + total += n + } + } + return total, errs +} + func (t *NativeTun) TXChecksumOffload() bool { return t.txChecksumOffload } diff --git a/tun_windows.go b/tun_windows.go index 392b78c..c63dbb7 100644 --- a/tun_windows.go +++ b/tun_windows.go @@ -148,6 +148,10 @@ func (t *NativeTun) configure() error { return nil } +func (t *NativeTun) Name() (string, error) { + return t.options.Name, nil +} + func (t *NativeTun) Start() error { if !t.options.AutoRoute { return nil