Refactor api

This commit is contained in:
世界 2022-08-14 11:52:38 +08:00
parent d378b6ca53
commit b828f01643
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
15 changed files with 357 additions and 138 deletions

View file

@ -3,6 +3,7 @@ name: Debug build
on: on:
push: push:
branches: branches:
- main
- dev - dev
paths-ignore: paths-ignore:
- '**.md' - '**.md'
@ -29,7 +30,7 @@ jobs:
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: ${{ steps.version.outputs.go_version }} go-version: ${{ steps.version.outputs.go_version }}
- name: Build - name: Add cache to Go proxy
run: | run: |
version=`git rev-parse HEAD` version=`git rev-parse HEAD`
mkdir build mkdir build
@ -37,4 +38,9 @@ jobs:
go mod init build go mod init build
go get -v github.com/sagernet/sing-tun@$version go get -v github.com/sagernet/sing-tun@$version
popd popd
go build -v . continue-on-error: true
- name: Build
run: |
make test
make lint_install
make lint

View file

@ -3,14 +3,14 @@ linters:
enable: enable:
- gofumpt - gofumpt
- govet - govet
- gci # - gci
- staticcheck - staticcheck
linters-settings: linters-settings:
gci: # gci:
sections: # sections:
- standard # - standard
- prefix(github.com/sagernet/sing) # - prefix(github.com/sagernet/sing)
- default # - default
staticcheck: staticcheck:
go: '1.18' go: '1.19'

20
Makefile Normal file
View file

@ -0,0 +1,20 @@
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write -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
lint:
GOOS=linux golangci-lint run .
GOOS=windows golangci-lint run .
GOOS=darwin golangci-lint run .
GOOS=freebsd golangci-lint run .
lint_install:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
test:
go test -v .

2
go.mod
View file

@ -5,7 +5,7 @@ go 1.18
require ( require (
github.com/eycorsican/go-tun2socks v1.16.11 github.com/eycorsican/go-tun2socks v1.16.11
github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805
github.com/sagernet/sing v0.0.0-20220807085721-583e78e0b86a github.com/sagernet/sing v0.0.0-20220814164830-4f2b872a8cbf
golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418
gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97 gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97

4
go.sum
View file

@ -4,8 +4,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs=
github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing v0.0.0-20220807085721-583e78e0b86a h1:EeaiaHqcGiGQdgRPHf8FPIKb17VADrncz1P27Jfli2w= github.com/sagernet/sing v0.0.0-20220814164830-4f2b872a8cbf h1:JYnx6N1H2KeFKM7YSH5+qfhvvcy6gpqSpewkp1RLRSg=
github.com/sagernet/sing v0.0.0-20220807085721-583e78e0b86a/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.0.0-20220814164830-4f2b872a8cbf/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=

21
lwip.go
View file

@ -54,6 +54,10 @@ func (l *LWIP) Start() error {
} }
func (l *LWIP) loopIn() { func (l *LWIP) loopIn() {
if winTun, isWintun := l.tun.(WinTun); isWintun {
l.loopInWintun(winTun)
return
}
mtu := int(l.tunMtu) mtu := int(l.tunMtu)
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
mtu += 4 mtu += 4
@ -84,6 +88,23 @@ func (l *LWIP) loopIn() {
} }
} }
func (l *LWIP) loopInWintun(tun WinTun) {
for {
packet, release, err := tun.ReadPacket()
if err != nil {
return
}
_, err = l.stack.Write(packet)
release()
if err != nil {
if err.Error() == "stack closed" {
return
}
l.handler.NewError(context.Background(), err)
}
}
}
func (l *LWIP) Close() error { func (l *LWIP) Close() error {
lwip.RegisterTCPConnHandler(nil) lwip.RegisterTCPConnHandler(nil)
lwip.RegisterUDPConnHandler(nil) lwip.RegisterUDPConnHandler(nil)

81
tun.go
View file

@ -2,9 +2,18 @@ package tun
import ( import (
"io" "io"
"net"
"net/netip"
"runtime"
"sort"
"strconv"
"strings"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ranges"
) )
type Handler interface { type Handler interface {
@ -17,3 +26,75 @@ type Tun interface {
io.ReadWriter io.ReadWriter
Close() error Close() error
} }
type WinTun interface {
Tun
ReadPacket() ([]byte, func(), error)
}
type Options struct {
Name string
Inet4Address netip.Prefix
Inet6Address netip.Prefix
MTU uint32
AutoRoute bool
IncludeUID []ranges.Range[uint32]
IncludeAndroidUser []int
ExcludeUID []ranges.Range[uint32]
}
func (o Options) ExcludedRanges() (uidRanges []ranges.Range[uint32]) {
var includeAndroidUser []int
if runtime.GOOS == "android" {
includeAndroidUser = o.IncludeAndroidUser
}
return buildExcludedRanges(o.IncludeUID, o.ExcludeUID, includeAndroidUser)
}
const (
androidUserRange = 100000
userEnd uint32 = 0xFFFFFFFF - 1
)
func buildExcludedRanges(includeRanges []ranges.Range[uint32], excludeRanges []ranges.Range[uint32], includeAndroidUser []int) (uidRanges []ranges.Range[uint32]) {
if len(includeRanges) > 0 {
uidRanges = includeRanges
}
if len(includeAndroidUser) > 0 {
includeAndroidUser = common.Uniq(includeAndroidUser)
sort.Ints(includeAndroidUser)
for _, androidUser := range includeAndroidUser {
uidRanges = append(uidRanges, ranges.New[uint32](uint32(androidUser)*androidUserRange, uint32(androidUser+1)*androidUserRange-1))
}
}
if len(uidRanges) > 0 {
uidRanges = ranges.Exclude(uidRanges, excludeRanges)
uidRanges = ranges.Revert(0, userEnd, uidRanges)
} else {
uidRanges = excludeRanges
}
return ranges.Merge(uidRanges)
}
func DefaultInterfaceName() (tunName string) {
if runtime.GOOS == "darwin" {
tunName = "utun"
} else {
tunName = "tun"
}
interfaces, err := net.Interfaces()
if err != nil {
return
}
var tunIndex int
for _, netInterface := range interfaces {
if strings.HasPrefix(netInterface.Name, tunName) {
index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)
if parseErr == nil {
tunIndex = int(index) + 1
}
}
}
tunName = F.ToString(tunName, tunIndex)
return
}

View file

@ -10,8 +10,9 @@ import (
"unsafe" "unsafe"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw" N "github.com/sagernet/sing/common/network"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.org/x/net/route" "golang.org/x/net/route"
@ -19,18 +20,18 @@ import (
) )
type NativeTun struct { type NativeTun struct {
tunFd uintptr
tunFile *os.File tunFile *os.File
tunWriter N.VectorisedWriter
mtu uint32
inet4Address string inet4Address string
inet6Address string inet6Address string
mtu uint32
} }
func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) (Tun, error) { func Open(options Options) (Tun, error) {
ifIndex := -1 ifIndex := -1
_, err := fmt.Sscanf(name, "utun%d", &ifIndex) _, err := fmt.Sscanf(options.Name, "utun%d", &ifIndex)
if err != nil { if err != nil {
return nil, E.New("bad tun name: ", name) return nil, E.New("bad tun name: ", options.Name)
} }
tunFd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2) tunFd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
@ -38,19 +39,18 @@ func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu
return nil, err return nil, err
} }
err = configure(tunFd, ifIndex, name, inet4Address, inet6Address, mtu, autoRoute) err = configure(tunFd, ifIndex, options.Name, options)
if err != nil { if err != nil {
unix.Close(tunFd) unix.Close(tunFd)
return nil, err return nil, err
} }
nativeTun := &NativeTun{ nativeTun := &NativeTun{
tunFd: uintptr(tunFd),
tunFile: os.NewFile(uintptr(tunFd), "utun"), tunFile: os.NewFile(uintptr(tunFd), "utun"),
inet4Address: string(inet4Address.Addr().AsSlice()), mtu: options.MTU,
inet6Address: string(inet6Address.Addr().AsSlice()), inet4Address: string(options.Inet4Address.Addr().AsSlice()),
mtu: mtu, inet6Address: string(options.Inet6Address.Addr().AsSlice()),
} }
nativeTun.tunWriter, _ = bufio.CreateVectorisedWriter(nativeTun.tunFile)
runtime.SetFinalizer(nativeTun.tunFile, nil) runtime.SetFinalizer(nativeTun.tunFile, nil)
return nativeTun, nil return nativeTun, nil
} }
@ -63,7 +63,7 @@ func (t *NativeTun) Read(p []byte) (n int, err error) {
copy(p[:], p[4:]) copy(p[:], p[4:])
return n - 4, err*/ return n - 4, err*/
return t.tunFile.Write(p) return t.tunFile.Read(p)
} }
var ( var (
@ -78,7 +78,7 @@ func (t *NativeTun) Write(p []byte) (n int, err error) {
} else { } else {
packetHeader = packetHeader6[:] packetHeader = packetHeader6[:]
} }
_, err = rw.WriteV(t.tunFd, [][]byte{packetHeader, p}) _, err = bufio.WriteVectorised(t.tunWriter, [][]byte{packetHeader, p})
if err == nil { if err == nil {
n = len(p) n = len(p)
} }
@ -121,7 +121,7 @@ type addrLifetime6 struct {
Pltime uint32 Pltime uint32
} }
func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error { func configure(tunFd int, ifIndex int, name string, options Options) error {
ctlInfo := &unix.CtlInfo{} ctlInfo := &unix.CtlInfo{}
copy(ctlInfo.Name[:], utunControlName) copy(ctlInfo.Name[:], utunControlName)
err := unix.IoctlCtlInfo(tunFd, ctlInfo) err := unix.IoctlCtlInfo(tunFd, ctlInfo)
@ -145,28 +145,28 @@ func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, i
err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error { err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error {
var ifr unix.IfreqMTU var ifr unix.IfreqMTU
copy(ifr.Name[:], name) copy(ifr.Name[:], name)
ifr.MTU = int32(mtu) ifr.MTU = int32(options.MTU)
return unix.IoctlSetIfreqMTU(socketFd, &ifr) return unix.IoctlSetIfreqMTU(socketFd, &ifr)
}) })
if err != nil { if err != nil {
return err return err
} }
if inet4Address.IsValid() { if options.Inet4Address.IsValid() {
ifReq := ifAliasReq{ ifReq := ifAliasReq{
Addr: unix.RawSockaddrInet4{ Addr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4, Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET, Family: unix.AF_INET,
Addr: inet4Address.Addr().As4(), Addr: options.Inet4Address.Addr().As4(),
}, },
Dstaddr: unix.RawSockaddrInet4{ Dstaddr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4, Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET, Family: unix.AF_INET,
Addr: inet4Address.Addr().As4(), Addr: options.Inet4Address.Addr().As4(),
}, },
Mask: unix.RawSockaddrInet4{ Mask: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4, Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET, Family: unix.AF_INET,
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(inet4Address.Bits(), 32)).String()).As4(), Addr: netip.MustParseAddr(net.IP(net.CIDRMask(options.Inet4Address.Bits(), 32)).String()).As4(),
}, },
} }
copy(ifReq.Name[:], name) copy(ifReq.Name[:], name)
@ -185,17 +185,17 @@ func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, i
return err return err
} }
} }
if inet6Address.IsValid() { if options.Inet6Address.IsValid() {
ifReq6 := ifAliasReq6{ ifReq6 := ifAliasReq6{
Addr: unix.RawSockaddrInet6{ Addr: unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6, Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6, Family: unix.AF_INET6,
Addr: inet6Address.Addr().As16(), Addr: options.Inet6Address.Addr().As16(),
}, },
Mask: unix.RawSockaddrInet6{ Mask: unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6, Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6, Family: unix.AF_INET6,
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(inet6Address.Bits(), 128)).String()).As16(), Addr: netip.MustParseAddr(net.IP(net.CIDRMask(options.Inet6Address.Bits(), 128)).String()).As16(),
}, },
Flags: IN6_IFF_NODAD | IN6_IFF_SECURED, Flags: IN6_IFF_NODAD | IN6_IFF_SECURED,
Lifetime: addrLifetime6{ Lifetime: addrLifetime6{
@ -203,11 +203,11 @@ func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, i
Pltime: ND6_INFINITE_LIFETIME, Pltime: ND6_INFINITE_LIFETIME,
}, },
} }
if inet6Address.Bits() == 128 { if options.Inet6Address.Bits() == 128 {
ifReq6.Dstaddr = unix.RawSockaddrInet6{ ifReq6.Dstaddr = unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6, Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6, Family: unix.AF_INET6,
Addr: inet6Address.Addr().Next().As16(), Addr: options.Inet6Address.Addr().Next().As16(),
} }
} }
copy(ifReq6.Name[:], name) copy(ifReq6.Name[:], name)
@ -226,8 +226,8 @@ func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, i
return err return err
} }
} }
if autoRoute { if options.AutoRoute {
if inet4Address.IsValid() { if options.Inet4Address.IsValid() {
for _, subnet := range []netip.Prefix{ for _, subnet := range []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 0, 0, 0}), 8), netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 0, 0, 0}), 8),
netip.PrefixFrom(netip.AddrFrom4([4]byte{2, 0, 0, 0}), 7), netip.PrefixFrom(netip.AddrFrom4([4]byte{2, 0, 0, 0}), 7),
@ -238,15 +238,15 @@ func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, i
netip.PrefixFrom(netip.AddrFrom4([4]byte{64, 0, 0, 0}), 2), netip.PrefixFrom(netip.AddrFrom4([4]byte{64, 0, 0, 0}), 2),
netip.PrefixFrom(netip.AddrFrom4([4]byte{128, 0, 0, 0}), 1), netip.PrefixFrom(netip.AddrFrom4([4]byte{128, 0, 0, 0}), 1),
} { } {
err = addRoute(subnet, inet4Address.Addr()) err = addRoute(subnet, options.Inet4Address.Addr())
if err != nil { if err != nil {
return err return err
} }
} }
} }
if inet6Address.IsValid() { if options.Inet6Address.IsValid() {
subnet := netip.PrefixFrom(netip.AddrFrom16([16]byte{32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), 3) subnet := netip.PrefixFrom(netip.AddrFrom16([16]byte{32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), 3)
err = addRoute(subnet, inet6Address.Addr()) err = addRoute(subnet, options.Inet6Address.Addr())
if err != nil { if err != nil {
return err return err
} }

View file

@ -5,7 +5,7 @@ package tun
import ( import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/bufio"
"gvisor.dev/gvisor/pkg/bufferv2" "gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
@ -122,7 +122,7 @@ func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (
case header.IPv6ProtocolNumber: case header.IPv6ProtocolNumber:
packetHeader = packetHeader6[:] packetHeader = packetHeader6[:]
} }
_, err := rw.WriteV(e.tun.tunFd, append([][]byte{packetHeader}, packet.AsSlices()...)) _, err := bufio.WriteVectorised(e.tun.tunWriter, append([][]byte{packetHeader}, packet.AsSlices()...))
if err != nil { if err != nil {
return n, &tcpip.ErrAborted{} return n, &tcpip.ErrAborted{}
} }

View file

@ -15,32 +15,24 @@ import (
) )
type NativeTun struct { type NativeTun struct {
name string tunFd int
tunFd int tunFile *os.File
tunFile *os.File options Options
inet4Address netip.Prefix
inet6Address netip.Prefix
mtu uint32
autoRoute bool
} }
func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) (Tun, error) { func Open(options Options) (Tun, error) {
tunFd, err := open(name) tunFd, err := open(options.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tunLink, err := netlink.LinkByName(name) tunLink, err := netlink.LinkByName(options.Name)
if err != nil { if err != nil {
return nil, E.Errors(err, unix.Close(tunFd)) return nil, E.Errors(err, unix.Close(tunFd))
} }
nativeTun := &NativeTun{ nativeTun := &NativeTun{
name: name, tunFd: tunFd,
tunFd: tunFd, tunFile: os.NewFile(uintptr(tunFd), "tun"),
tunFile: os.NewFile(uintptr(tunFd), "tun"), options: options,
mtu: mtu,
inet4Address: inet4Address,
inet6Address: inet6Address,
autoRoute: autoRoute,
} }
runtime.SetFinalizer(nativeTun.tunFile, nil) runtime.SetFinalizer(nativeTun.tunFile, nil)
err = nativeTun.configure(tunLink) err = nativeTun.configure(tunLink)
@ -99,7 +91,7 @@ func open(name string) (int, error) {
} }
func (t *NativeTun) configure(tunLink netlink.Link) error { func (t *NativeTun) configure(tunLink netlink.Link) error {
err := netlink.LinkSetMTU(tunLink, int(t.mtu)) err := netlink.LinkSetMTU(tunLink, int(t.options.MTU))
if err == unix.EPERM { if err == unix.EPERM {
// unprivileged // unprivileged
return nil return nil
@ -107,16 +99,16 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
return err return err
} }
if t.inet4Address.IsValid() { if t.options.Inet4Address.IsValid() {
addr4, _ := netlink.ParseAddr(t.inet4Address.String()) addr4, _ := netlink.ParseAddr(t.options.Inet4Address.String())
err = netlink.AddrAdd(tunLink, addr4) err = netlink.AddrAdd(tunLink, addr4)
if err != nil { if err != nil {
return err return err
} }
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
addr6, _ := netlink.ParseAddr(t.inet6Address.String()) addr6, _ := netlink.ParseAddr(t.options.Inet6Address.String())
err = netlink.AddrAdd(tunLink, addr6) err = netlink.AddrAdd(tunLink, addr6)
if err != nil { if err != nil {
return err return err
@ -128,7 +120,7 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
return err return err
} }
if t.autoRoute { if t.options.AutoRoute {
_ = t.unsetRoute0(tunLink) _ = t.unsetRoute0(tunLink)
err = t.setRoute(tunLink) err = t.setRoute(tunLink)
if err != nil { if err != nil {
@ -141,7 +133,7 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
func (t *NativeTun) Close() error { func (t *NativeTun) Close() error {
var errors []error var errors []error
if t.autoRoute { if t.options.AutoRoute {
errors = append(errors, t.unsetRoute()) errors = append(errors, t.unsetRoute())
} }
return E.Errors(append(errors, t.tunFile.Close())...) return E.Errors(append(errors, t.tunFile.Close())...)
@ -151,7 +143,7 @@ const tunTableIndex = 2022
func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route { func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route {
var routes []netlink.Route var routes []netlink.Route
if t.inet4Address.IsValid() { if t.options.Inet4Address.IsValid() {
routes = append(routes, netlink.Route{ routes = append(routes, netlink.Route{
Dst: &net.IPNet{ Dst: &net.IPNet{
IP: net.IPv4zero, IP: net.IPv4zero,
@ -161,7 +153,7 @@ func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route {
Table: tunTableIndex, Table: tunTableIndex,
}) })
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
routes = append(routes, netlink.Route{ routes = append(routes, netlink.Route{
Dst: &net.IPNet{ Dst: &net.IPNet{
IP: net.IPv6zero, IP: net.IPv6zero,
@ -176,21 +168,24 @@ func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route {
func (t *NativeTun) rules() []*netlink.Rule { func (t *NativeTun) rules() []*netlink.Rule {
var rules []*netlink.Rule var rules []*netlink.Rule
var it *netlink.Rule
excludeRanges := t.options.ExcludedRanges()
priority := 9000 priority := 9000
nopPriority := priority + 10*(len(excludeRanges)/10+1)
it := netlink.NewRule() for _, excludeRange := range t.options.ExcludedRanges() {
it.Priority = priority
it.Invert = true
it.UIDRange = netlink.NewRuleUIDRange(0, 0xFFFFFFFF-1)
it.Goto = 9100
rules = append(rules, it)
priority++
if t.inet4Address.IsValid() {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.Dst = t.inet4Address.Masked() it.UIDRange = netlink.NewRuleUIDRange(uint32(excludeRange.Start), uint32(excludeRange.End))
it.Goto = nopPriority
rules = append(rules, it)
priority++
}
if t.options.Inet4Address.IsValid() {
it = netlink.NewRule()
it.Priority = priority
it.Dst = t.options.Inet4Address.Masked()
it.Table = tunTableIndex it.Table = tunTableIndex
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
@ -198,15 +193,15 @@ func (t *NativeTun) rules() []*netlink.Rule {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.IPProto = unix.IPPROTO_ICMP it.IPProto = unix.IPPROTO_ICMP
it.Goto = 9100 it.Goto = nopPriority
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.Dst = t.inet6Address.Masked() it.Dst = t.options.Inet6Address.Masked()
it.Table = tunTableIndex it.Table = tunTableIndex
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
@ -214,7 +209,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.IPProto = unix.IPPROTO_ICMPV6 it.IPProto = unix.IPPROTO_ICMPV6
it.Goto = 9100 it.Goto = nopPriority
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
} }
@ -244,28 +239,28 @@ func (t *NativeTun) rules() []*netlink.Rule {
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
if t.inet4Address.IsValid() { if t.options.Inet4Address.IsValid() {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.IifName = "lo" it.IifName = "lo"
it.Src = t.inet4Address.Masked() it.Src = t.options.Inet4Address.Masked()
it.Table = tunTableIndex it.Table = tunTableIndex
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.IifName = "lo" it.IifName = "lo"
it.Src = t.inet6Address.Masked() it.Src = t.options.Inet6Address.Masked()
it.Table = tunTableIndex it.Table = tunTableIndex
rules = append(rules, it) rules = append(rules, it)
priority++ priority++
} }
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = 9100 it.Priority = nopPriority
rules = append(rules, it) rules = append(rules, it)
return rules return rules
@ -288,7 +283,7 @@ func (t *NativeTun) setRoute(tunLink netlink.Link) error {
} }
func (t *NativeTun) unsetRoute() error { func (t *NativeTun) unsetRoute() error {
tunLink, err := netlink.LinkByName(t.name) tunLink, err := netlink.LinkByName(t.options.Name)
if err != nil { if err != nil {
return err return err
} }

View file

@ -12,6 +12,6 @@ var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) { func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{ return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd}, FDs: []int{t.tunFd},
MTU: t.mtu, MTU: t.options.MTU,
}) })
} }

View file

@ -3,10 +3,9 @@
package tun package tun
import ( import (
"net/netip"
"os" "os"
) )
func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) (Tun, error) { func Open(config Options) (Tun, error) {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }

70
tun_test.go Normal file
View file

@ -0,0 +1,70 @@
package tun
import (
"reflect"
"testing"
"github.com/sagernet/sing/common/ranges"
)
func TestUIDRanges(t *testing.T) {
for _, testRange := range []struct {
include []ranges.Range[uint32]
exclude []ranges.Range[uint32]
androidUser []int
expected []ranges.Range[uint32]
}{
{},
{
include: []ranges.Range[uint32]{
ranges.NewSingle[uint32](0),
ranges.NewSingle[uint32](1000),
},
expected: []ranges.Range[uint32]{
{Start: 1, End: 999},
{Start: 1001, End: userEnd},
},
},
{
androidUser: []int{0},
expected: []ranges.Range[uint32]{
{Start: androidUserRange, End: userEnd},
},
},
{
androidUser: []int{0},
expected: []ranges.Range[uint32]{
{Start: 100000, End: userEnd},
},
},
{
androidUser: []int{10},
include: []ranges.Range[uint32]{
ranges.NewSingle[uint32](0),
},
expected: []ranges.Range[uint32]{
{Start: 1, End: androidUserRange*10 - 1},
{Start: androidUserRange * 11, End: userEnd},
},
},
{
include: []ranges.Range[uint32]{
{Start: 123456, End: 123456},
},
exclude: []ranges.Range[uint32]{
{Start: 1000, End: 1000},
},
androidUser: []int{0},
expected: []ranges.Range[uint32]{
{Start: 1000, End: 1000},
{Start: 100000, End: 123455},
{Start: 123457, End: userEnd},
},
},
} {
result := buildExcludedRanges(testRange.include, testRange.exclude, testRange.androidUser)
if !reflect.DeepEqual(result, testRange.expected) {
t.Fatal("input", testRange.include, testRange.exclude, testRange.androidUser, "\nexpected", testRange.expected, "\ngot", result)
}
}
}

View file

@ -21,33 +21,34 @@ import (
var TunnelType = "sing-tun" var TunnelType = "sing-tun"
type NativeTun struct { type NativeTun struct {
adapter *wintun.Adapter adapter *wintun.Adapter
inet4Address netip.Prefix options Options
inet6Address netip.Prefix session wintun.Session
mtu uint32 readWait windows.Handle
autoRoute bool rate rateJuggler
session wintun.Session running sync.WaitGroup
readWait windows.Handle closeOnce sync.Once
rate rateJuggler close int32
running sync.WaitGroup
closeOnce sync.Once
close int32
} }
func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) (Tun, error) { func Open(options Options) (WinTun, error) {
adapter, err := wintun.CreateAdapter(name, TunnelType, generateGUIDByDeviceName(name)) adapter, err := wintun.CreateAdapter(options.Name, TunnelType, generateGUIDByDeviceName(options.Name))
if err != nil { if err != nil {
return nil, err return nil, err
} }
nativeTun := &NativeTun{ nativeTun := &NativeTun{
adapter: adapter, adapter: adapter,
inet4Address: inet4Address, options: options,
inet6Address: inet6Address,
mtu: mtu,
autoRoute: autoRoute,
} }
session, err := adapter.StartSession(0x800000)
if err != nil {
return nil, err
}
nativeTun.session = session
nativeTun.readWait = session.ReadWaitEvent()
err = nativeTun.configure() err = nativeTun.configure()
if err != nil { if err != nil {
session.End()
adapter.Close() adapter.Close()
return nil, err return nil, err
} }
@ -56,41 +57,41 @@ func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu
func (t *NativeTun) configure() error { func (t *NativeTun) configure() error {
luid := winipcfg.LUID(t.adapter.LUID()) luid := winipcfg.LUID(t.adapter.LUID())
if t.inet4Address.IsValid() { if t.options.Inet4Address.IsValid() {
err := luid.SetIPAddressesForFamily(winipcfg.AddressFamily(windows.AF_INET), []netip.Prefix{t.inet4Address}) err := luid.SetIPAddressesForFamily(winipcfg.AddressFamily(windows.AF_INET), []netip.Prefix{t.options.Inet4Address})
if err != nil { if err != nil {
return E.Cause(err, "set ipv4 address") return E.Cause(err, "set ipv4 address")
} }
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
err := luid.SetIPAddressesForFamily(winipcfg.AddressFamily(windows.AF_INET6), []netip.Prefix{t.inet6Address}) err := luid.SetIPAddressesForFamily(winipcfg.AddressFamily(windows.AF_INET6), []netip.Prefix{t.options.Inet6Address})
if err != nil { if err != nil {
return E.Cause(err, "set ipv6 address") return E.Cause(err, "set ipv6 address")
} }
} }
err := luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), []netip.Addr{t.inet4Address.Addr().Next()}, nil) err := luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), []netip.Addr{t.options.Inet4Address.Addr().Next()}, nil)
if err != nil { if err != nil {
return E.Cause(err, "set ipv4 dns") return E.Cause(err, "set ipv4 dns")
} }
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), []netip.Addr{t.inet6Address.Addr().Next()}, nil) err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), []netip.Addr{t.options.Inet6Address.Addr().Next()}, nil)
if err != nil { if err != nil {
return E.Cause(err, "set ipv6 dns") return E.Cause(err, "set ipv6 dns")
} }
if t.autoRoute { if t.options.AutoRoute {
if t.inet4Address.IsValid() { if t.options.Inet4Address.IsValid() {
err = luid.AddRoute(netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.IPv4Unspecified(), 0) err = luid.AddRoute(netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.IPv4Unspecified(), 0)
if err != nil { if err != nil {
return E.Cause(err, "set ipv4 route") return E.Cause(err, "set ipv4 route")
} }
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
err = luid.AddRoute(netip.PrefixFrom(netip.IPv6Unspecified(), 0), netip.IPv6Unspecified(), 0) err = luid.AddRoute(netip.PrefixFrom(netip.IPv6Unspecified(), 0), netip.IPv6Unspecified(), 0)
if err != nil { if err != nil {
return E.Cause(err, "set ipv6 route") return E.Cause(err, "set ipv6 route")
} }
} }
} }
if t.inet4Address.IsValid() { if t.options.Inet4Address.IsValid() {
var inetIf *winipcfg.MibIPInterfaceRow var inetIf *winipcfg.MibIPInterfaceRow
inetIf, err = luid.IPInterface(winipcfg.AddressFamily(windows.AF_INET)) inetIf, err = luid.IPInterface(winipcfg.AddressFamily(windows.AF_INET))
if err != nil { if err != nil {
@ -101,8 +102,8 @@ func (t *NativeTun) configure() error {
inetIf.DadTransmits = 0 inetIf.DadTransmits = 0
inetIf.ManagedAddressConfigurationSupported = false inetIf.ManagedAddressConfigurationSupported = false
inetIf.OtherStatefulConfigurationSupported = false inetIf.OtherStatefulConfigurationSupported = false
inetIf.NLMTU = t.mtu inetIf.NLMTU = t.options.MTU
if t.autoRoute { if t.options.AutoRoute {
inetIf.UseAutomaticMetric = false inetIf.UseAutomaticMetric = false
inetIf.Metric = 0 inetIf.Metric = 0
} }
@ -111,7 +112,7 @@ func (t *NativeTun) configure() error {
return E.Cause(err, "set ipv4 options") return E.Cause(err, "set ipv4 options")
} }
} }
if t.inet6Address.IsValid() { if t.options.Inet6Address.IsValid() {
var inet6If *winipcfg.MibIPInterfaceRow var inet6If *winipcfg.MibIPInterfaceRow
inet6If, err = luid.IPInterface(winipcfg.AddressFamily(windows.AF_INET6)) inet6If, err = luid.IPInterface(winipcfg.AddressFamily(windows.AF_INET6))
if err != nil { if err != nil {
@ -121,8 +122,8 @@ func (t *NativeTun) configure() error {
inet6If.DadTransmits = 0 inet6If.DadTransmits = 0
inet6If.ManagedAddressConfigurationSupported = false inet6If.ManagedAddressConfigurationSupported = false
inet6If.OtherStatefulConfigurationSupported = false inet6If.OtherStatefulConfigurationSupported = false
inet6If.NLMTU = t.mtu inet6If.NLMTU = t.options.MTU
if t.autoRoute { if t.options.AutoRoute {
inet6If.UseAutomaticMetric = false inet6If.UseAutomaticMetric = false
inet6If.Metric = 0 inet6If.Metric = 0
} }
@ -136,10 +137,42 @@ func (t *NativeTun) configure() error {
} }
func (t *NativeTun) Read(p []byte) (n int, err error) { func (t *NativeTun) Read(p []byte) (n int, err error) {
err = t.ReadFunc(func(b []byte) { return 0, os.ErrInvalid
n = copy(p, b) }
})
return func (t *NativeTun) ReadPacket() ([]byte, func(), error) {
t.running.Add(1)
defer t.running.Done()
retry:
if atomic.LoadInt32(&t.close) == 1 {
return nil, nil, os.ErrClosed
}
start := nanotime()
shouldSpin := atomic.LoadUint64(&t.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&t.rate.nextStartTime)) <= rateMeasurementGranularity*2
for {
if atomic.LoadInt32(&t.close) == 1 {
return nil, nil, os.ErrClosed
}
packet, err := t.session.ReceivePacket()
switch err {
case nil:
packetSize := len(packet)
t.rate.update(uint64(packetSize))
return packet, func() { t.session.ReleaseReceivePacket(packet) }, nil
case windows.ERROR_NO_MORE_ITEMS:
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
windows.WaitForSingleObject(t.readWait, windows.INFINITE)
goto retry
}
procyield(1)
continue
case windows.ERROR_HANDLE_EOF:
return nil, nil, os.ErrClosed
case windows.ERROR_INVALID_DATA:
return nil, nil, errors.New("send ring corrupt")
}
return nil, nil, fmt.Errorf("read failed: %w", err)
}
} }
func (t *NativeTun) ReadFunc(block func(b []byte)) error { func (t *NativeTun) ReadFunc(block func(b []byte)) error {

View file

@ -12,12 +12,6 @@ import (
var _ GVisorTun = (*NativeTun)(nil) var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) { func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
session, err := t.adapter.StartSession(0x800000)
if err != nil {
return nil, err
}
t.session = session
t.readWait = session.ReadWaitEvent()
return &WintunEndpoint{tun: t}, nil return &WintunEndpoint{tun: t}, nil
} }
@ -29,7 +23,7 @@ type WintunEndpoint struct {
} }
func (e *WintunEndpoint) MTU() uint32 { func (e *WintunEndpoint) MTU() uint32 {
return e.tun.mtu return e.tun.options.MTU
} }
func (e *WintunEndpoint) MaxHeaderLength() uint16 { func (e *WintunEndpoint) MaxHeaderLength() uint16 {