Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4efde6372e | ||
|
|
1c6d2891ab | ||
|
|
5d9bd04495 | ||
|
|
5bf54dc69a | ||
|
|
840f3758f9 | ||
|
|
d923e5d10a | ||
|
|
779d1c7db2 | ||
|
|
3f128a4a6a | ||
|
|
fb6e917a2c | ||
|
|
e272ff0ad3 | ||
|
|
5584917e52 | ||
|
|
e0ddbbb84f | ||
|
|
a9895a7d88 | ||
|
|
9380493c39 | ||
|
|
63f6630a0a | ||
|
|
d174625727 | ||
|
|
520d1bc9bb | ||
|
|
fc63ec9388 | ||
|
|
cddf60537d | ||
|
|
8bfb64cf04 | ||
|
|
689e60891c | ||
|
|
6ef2a6cdaa | ||
|
|
8d285f70fb | ||
|
|
e8633c66d2 | ||
|
|
951af3ca7a | ||
|
|
9b7c2a0a3c | ||
|
|
38c945fec5 | ||
|
|
a276461b88 | ||
|
|
ebb3908ecf | ||
|
|
5b50c61b72 | ||
|
|
fa89d2c0a5 | ||
|
|
cad35277a2 | ||
|
|
69c3b72eec | ||
|
|
62f2d98190 | ||
|
|
d275b4a0fd | ||
|
|
46adeb9b5d | ||
|
|
f6ea97c5af | ||
|
|
3fa4ee409a | ||
|
|
958d6a25a4 | ||
|
|
86322a3fe1 | ||
|
|
78e0dfa18f | ||
|
|
ce9c864d89 | ||
|
|
da350ecc72 | ||
|
|
1a00992d06 | ||
|
|
150b116231 | ||
|
|
b93db9639d | ||
|
|
efd9884154 | ||
|
|
1a85bd3ef4 | ||
|
|
56a9b85cf5 | ||
|
|
660222a0dd | ||
|
|
fee2614ae3 | ||
|
|
2b625a47c0 | ||
|
|
dcf7d50379 | ||
|
|
4979f75513 | ||
|
|
2a0a0ab228 | ||
|
|
8adce0ea02 | ||
|
|
b6d323004e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/.idea/
|
||||
/vendor/
|
||||
.DS_Store
|
||||
|
||||
17
go.mod
17
go.mod
@@ -3,19 +3,18 @@ module github.com/sagernet/sing-tun
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61
|
||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.2.11
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/sys v0.12.0
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba
|
||||
github.com/sagernet/sing v0.4.1
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
)
|
||||
|
||||
41
go.sum
41
go.sum
@@ -1,29 +1,28 @@
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
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/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
|
||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
|
||||
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.2.11 h1:mu0S6d8y/xSVxilOqRd32Fmire5SZz9nT3t9NEHwUMY=
|
||||
github.com/sagernet/sing v0.2.11/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
|
||||
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
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.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -50,6 +50,10 @@ func (p TCPPacket) SetChecksum(sum [2]byte) {
|
||||
p[17] = sum[1]
|
||||
}
|
||||
|
||||
func (p TCPPacket) OffloadChecksum() {
|
||||
p.SetChecksum(zeroChecksum)
|
||||
}
|
||||
|
||||
func (p TCPPacket) ResetChecksum(psum uint32) {
|
||||
p.SetChecksum(zeroChecksum)
|
||||
p.SetChecksum(Checksum(psum, p))
|
||||
|
||||
@@ -45,6 +45,10 @@ func (p UDPPacket) SetChecksum(sum [2]byte) {
|
||||
p[7] = sum[1]
|
||||
}
|
||||
|
||||
func (p UDPPacket) OffloadChecksum() {
|
||||
p.SetChecksum(zeroChecksum)
|
||||
}
|
||||
|
||||
func (p UDPPacket) ResetChecksum(psum uint32) {
|
||||
p.SetChecksum(zeroChecksum)
|
||||
p.SetChecksum(Checksum(psum, p))
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
"github.com/go-ole/go-ole/oleutil"
|
||||
"github.com/scjalliance/comshim"
|
||||
)
|
||||
|
||||
// Firewall related API constants.
|
||||
@@ -250,7 +249,10 @@ func FirewallRuleExistsByName(rules *ole.IDispatch, name string) (bool, error) {
|
||||
// then:
|
||||
// dispatch firewallAPIRelease(u, fwp)
|
||||
func firewallAPIInit() (*ole.IUnknown, *ole.IDispatch, error) {
|
||||
comshim.Add(1)
|
||||
err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to initialize COM: %s", err)
|
||||
}
|
||||
|
||||
unknown, err := oleutil.CreateObject("HNetCfg.FwPolicy2")
|
||||
if err != nil {
|
||||
@@ -270,5 +272,5 @@ func firewallAPIInit() (*ole.IUnknown, *ole.IDispatch, error) {
|
||||
func firewallAPIRelease(u *ole.IUnknown, fwp *ole.IDispatch) {
|
||||
fwp.Release()
|
||||
u.Release()
|
||||
comshim.Done()
|
||||
ole.CoUninitialize()
|
||||
}
|
||||
|
||||
@@ -385,3 +385,25 @@ func (luid LUID) SetDNS(family AddressFamily, servers []netip.Addr, domains []st
|
||||
func (luid LUID) FlushDNS(family AddressFamily) error {
|
||||
return luid.SetDNS(family, nil, nil)
|
||||
}
|
||||
|
||||
func (luid LUID) DisableDNSRegistration() error {
|
||||
guid, err := luid.GUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dnsInterfaceSettings := &DnsInterfaceSettings{
|
||||
Version: DnsInterfaceSettingsVersion1,
|
||||
Flags: DnsInterfaceSettingsFlagRegistrationEnabled,
|
||||
RegistrationEnabled: 0,
|
||||
}
|
||||
|
||||
// For >= Windows 10 1809
|
||||
err = SetInterfaceDnsSettings(*guid, dnsInterfaceSettings)
|
||||
if err == nil || !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
|
||||
return err
|
||||
}
|
||||
|
||||
// For < Windows 10 1809
|
||||
return luid.fallbackDisableDNSRegistration()
|
||||
}
|
||||
|
||||
@@ -51,10 +51,11 @@ func runNetsh(cmds []string) error {
|
||||
}
|
||||
|
||||
const (
|
||||
netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no register=both"
|
||||
netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no register=both"
|
||||
netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no"
|
||||
netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no"
|
||||
netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no"
|
||||
netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no"
|
||||
netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no"
|
||||
netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no"
|
||||
netshCmdTemplateDisableRegistration = "interface ipv6 set dnsservers name=%d register=none"
|
||||
)
|
||||
|
||||
func (luid LUID) fallbackSetDNSForFamily(family AddressFamily, dnses []netip.Addr) error {
|
||||
@@ -106,3 +107,13 @@ func (luid LUID) fallbackSetDNSDomain(domain string) error {
|
||||
key.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (luid LUID) fallbackDisableDNSRegistration() error {
|
||||
// the DNS registration setting is shared for both IPv4 and IPv6
|
||||
ipif, err := luid.IPInterface(windows.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := fmt.Sprintf(netshCmdTemplateDisableRegistration, ipif.InterfaceIndex)
|
||||
return runNetsh([]string{cmd})
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type DefaultInterfaceMonitor interface {
|
||||
Close() error
|
||||
DefaultInterfaceName(destination netip.Addr) string
|
||||
DefaultInterfaceIndex(destination netip.Addr) int
|
||||
DefaultInterface(destination netip.Addr) (string, int)
|
||||
OverrideAndroidVPN() bool
|
||||
AndroidVPNEnabled() bool
|
||||
RegisterCallback(callback DefaultInterfaceUpdateCallback) *list.Element[DefaultInterfaceUpdateCallback]
|
||||
|
||||
@@ -42,7 +42,7 @@ func (m *networkUpdateMonitor) loopUpdate() {
|
||||
select {
|
||||
case <-m.done:
|
||||
return
|
||||
case <-time.After(time.Second):
|
||||
default:
|
||||
}
|
||||
err := m.loopUpdate0()
|
||||
if err != nil {
|
||||
@@ -57,17 +57,31 @@ func (m *networkUpdateMonitor) loopUpdate0() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = unix.SetNonblock(routeSocket, true)
|
||||
if err != nil {
|
||||
unix.Close(routeSocket)
|
||||
return err
|
||||
}
|
||||
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
|
||||
defer routeSocketFile.Close()
|
||||
m.routeSocketFile = routeSocketFile
|
||||
m.loopUpdate1(routeSocketFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *networkUpdateMonitor) loopUpdate1(routeSocketFile *os.File) {
|
||||
defer routeSocketFile.Close()
|
||||
buffer := buf.NewPacket()
|
||||
defer buffer.Release()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-m.done:
|
||||
routeSocketFile.Close()
|
||||
case <-done:
|
||||
}
|
||||
}()
|
||||
n, err := routeSocketFile.Read(buffer.FreeBytes())
|
||||
close(done)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -92,57 +106,59 @@ func (m *networkUpdateMonitor) Close() error {
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
ribMessage, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routeMessages, err := route.ParseRIB(route.RIBTypeRoute, ribMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var defaultInterface *net.Interface
|
||||
for _, rawRouteMessage := range routeMessages {
|
||||
routeMessage := rawRouteMessage.(*route.RouteMessage)
|
||||
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)
|
||||
var (
|
||||
defaultInterface *net.Interface
|
||||
err error
|
||||
)
|
||||
if m.options.UnderNetworkExtension {
|
||||
defaultInterface, err = getDefaultInterfaceBySocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_UP == 0 {
|
||||
continue
|
||||
} else {
|
||||
ribMessage, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
||||
continue
|
||||
routeMessages, err := route.ParseRIB(route.RIBTypeRoute, ribMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_IFSCOPE != 0 {
|
||||
// continue
|
||||
}
|
||||
defaultInterface = routeInterface
|
||||
break
|
||||
}
|
||||
if defaultInterface == nil {
|
||||
if m.options.UnderNetworkExtension {
|
||||
defaultInterface, err = getDefaultInterfaceBySocket()
|
||||
for _, rawRouteMessage := range routeMessages {
|
||||
routeMessage := rawRouteMessage.(*route.RouteMessage)
|
||||
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 {
|
||||
@@ -170,6 +186,8 @@ func getDefaultInterfaceBySocket() (*net.Interface, error) {
|
||||
Port: 80,
|
||||
})
|
||||
result := make(chan netip.Addr, 1)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
for {
|
||||
sockname, sockErr := unix.Getsockname(socketFd)
|
||||
@@ -182,8 +200,13 @@ func getDefaultInterfaceBySocket() (*net.Interface, error) {
|
||||
}
|
||||
addr := netip.AddrFrom4(sockaddr.Addr)
|
||||
if addr.IsUnspecified() {
|
||||
time.Sleep(time.Millisecond)
|
||||
continue
|
||||
select {
|
||||
case <-done:
|
||||
break
|
||||
default:
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
result <- addr
|
||||
break
|
||||
@@ -193,7 +216,7 @@ func getDefaultInterfaceBySocket() (*net.Interface, error) {
|
||||
select {
|
||||
case selectedAddr = <-result:
|
||||
case <-time.After(time.Second):
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
return nil, nil
|
||||
}
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
|
||||
@@ -2,11 +2,16 @@ package tun
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/netlink"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type networkUpdateMonitor struct {
|
||||
@@ -19,13 +24,34 @@ type networkUpdateMonitor struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
var ErrNetlinkBanned = E.New(
|
||||
"netlink socket in Android is banned by Google, " +
|
||||
"use the root or system (ADB) user to run sing-box, " +
|
||||
"or switch to the sing-box Adnroid graphical interface client",
|
||||
)
|
||||
|
||||
func NewNetworkUpdateMonitor(logger logger.Logger) (NetworkUpdateMonitor, error) {
|
||||
return &networkUpdateMonitor{
|
||||
monitor := &networkUpdateMonitor{
|
||||
routeUpdate: make(chan netlink.RouteUpdate, 2),
|
||||
linkUpdate: make(chan netlink.LinkUpdate, 2),
|
||||
close: make(chan struct{}),
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
// check is netlink banned by google
|
||||
if runtime.GOOS == "android" {
|
||||
netlinkSocket, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_DGRAM, unix.NETLINK_ROUTE)
|
||||
if err != nil {
|
||||
return nil, ErrNetlinkBanned
|
||||
}
|
||||
err = unix.Bind(netlinkSocket, &unix.SockaddrNetlink{
|
||||
Family: unix.AF_NETLINK,
|
||||
})
|
||||
unix.Close(netlinkSocket)
|
||||
if err != nil {
|
||||
return nil, ErrNetlinkBanned
|
||||
}
|
||||
}
|
||||
return monitor, nil
|
||||
}
|
||||
|
||||
func (m *networkUpdateMonitor) Start() error {
|
||||
@@ -42,6 +68,9 @@ func (m *networkUpdateMonitor) Start() error {
|
||||
}
|
||||
|
||||
func (m *networkUpdateMonitor) loopUpdate() {
|
||||
const minDuration = time.Second
|
||||
timer := time.NewTimer(minDuration)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-m.close:
|
||||
@@ -50,6 +79,12 @@ func (m *networkUpdateMonitor) loopUpdate() {
|
||||
case <-m.linkUpdate:
|
||||
}
|
||||
m.emit()
|
||||
select {
|
||||
case <-m.close:
|
||||
return
|
||||
case <-timer.C:
|
||||
timer.Reset(minDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ type defaultInterfaceMonitor struct {
|
||||
defaultInterfaceName string
|
||||
defaultInterfaceIndex int
|
||||
androidVPNEnabled bool
|
||||
noRoute bool
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
checkUpdateTimer *time.Timer
|
||||
element *list.Element[NetworkUpdateCallback]
|
||||
access sync.Mutex
|
||||
callbacks list.List[DefaultInterfaceUpdateCallback]
|
||||
@@ -65,25 +67,36 @@ func NewDefaultInterfaceMonitor(networkMonitor NetworkUpdateMonitor, logger logg
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) Start() error {
|
||||
err := m.checkUpdate()
|
||||
if err != nil {
|
||||
m.logger.Error("initialize default interface: ", err)
|
||||
}
|
||||
_ = m.checkUpdate()
|
||||
m.element = m.networkMonitor.RegisterCallback(m.delayCheckUpdate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) delayCheckUpdate() {
|
||||
time.Sleep(time.Second)
|
||||
if m.checkUpdateTimer == nil {
|
||||
m.checkUpdateTimer = time.AfterFunc(time.Second, m.postCheckUpdate)
|
||||
} else {
|
||||
m.checkUpdateTimer.Reset(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) postCheckUpdate() {
|
||||
err := m.updateInterfaces()
|
||||
if err != nil {
|
||||
m.logger.Error("update interfaces: ", err)
|
||||
}
|
||||
err = m.checkUpdate()
|
||||
if errors.Is(err, ErrNoRoute) {
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.emit(EventNoRoute)
|
||||
if !m.noRoute {
|
||||
m.noRoute = true
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.emit(EventNoRoute)
|
||||
}
|
||||
} else if err != nil {
|
||||
m.logger.Error("check interface: ", err)
|
||||
} else {
|
||||
m.noRoute = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +143,6 @@ func (m *defaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) s
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.defaultInterfaceIndex == -1 {
|
||||
m.checkUpdate()
|
||||
}
|
||||
return m.defaultInterfaceName
|
||||
}
|
||||
|
||||
@@ -144,12 +154,20 @@ func (m *defaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.defaultInterfaceIndex == -1 {
|
||||
m.checkUpdate()
|
||||
}
|
||||
return m.defaultInterfaceIndex
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.interfaceName, address.interfaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.defaultInterfaceName, m.defaultInterfaceIndex
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) OverrideAndroidVPN() bool {
|
||||
return m.options.OverrideAndroidVPN
|
||||
}
|
||||
|
||||
30
stack.go
30
stack.go
@@ -2,6 +2,8 @@ package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -17,15 +19,13 @@ type Stack interface {
|
||||
type StackOptions struct {
|
||||
Context context.Context
|
||||
Tun Tun
|
||||
Name string
|
||||
MTU uint32
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
TunOptions Options
|
||||
EndpointIndependentNat bool
|
||||
UDPTimeout int64
|
||||
Handler Handler
|
||||
Logger logger.Logger
|
||||
ForwarderBindInterface bool
|
||||
IncludeAllNetworks bool
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ func NewStack(
|
||||
) (Stack, error) {
|
||||
switch stack {
|
||||
case "":
|
||||
if WithGVisor {
|
||||
if options.IncludeAllNetworks {
|
||||
return NewGVisor(options)
|
||||
} else if WithGVisor && !options.TunOptions.GSO {
|
||||
return NewMixed(options)
|
||||
} else {
|
||||
return NewSystem(options)
|
||||
@@ -43,12 +45,26 @@ func NewStack(
|
||||
case "gvisor":
|
||||
return NewGVisor(options)
|
||||
case "mixed":
|
||||
if options.IncludeAllNetworks {
|
||||
return nil, ErrIncludeAllNetworks
|
||||
}
|
||||
return NewMixed(options)
|
||||
case "system":
|
||||
if options.IncludeAllNetworks {
|
||||
return nil, ErrIncludeAllNetworks
|
||||
}
|
||||
return NewSystem(options)
|
||||
case "lwip":
|
||||
return NewLWIP(options)
|
||||
default:
|
||||
return nil, E.New("unknown stack: ", stack)
|
||||
}
|
||||
}
|
||||
|
||||
func BroadcastAddr(inet4Address []netip.Prefix) netip.Addr {
|
||||
if len(inet4Address) == 0 {
|
||||
return netip.Addr{}
|
||||
}
|
||||
prefix := inet4Address[0]
|
||||
var broadcastAddr [4]byte
|
||||
binary.BigEndian.PutUint32(broadcastAddr[:], binary.BigEndian.Uint32(prefix.Masked().Addr().AsSlice())|^binary.BigEndian.Uint32(net.CIDRMask(prefix.Bits(), 32)))
|
||||
return netip.AddrFrom4(broadcastAddr)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
||||
@@ -32,9 +31,9 @@ const defaultNIC tcpip.NICID = 1
|
||||
type GVisor struct {
|
||||
ctx context.Context
|
||||
tun GVisorTun
|
||||
tunMtu uint32
|
||||
endpointIndependentNat bool
|
||||
udpTimeout int64
|
||||
broadcastAddr netip.Addr
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
stack *stack.Stack
|
||||
@@ -57,9 +56,9 @@ func NewGVisor(
|
||||
gStack := &GVisor{
|
||||
ctx: options.Context,
|
||||
tun: gTun,
|
||||
tunMtu: options.MTU,
|
||||
endpointIndependentNat: options.EndpointIndependentNat,
|
||||
udpTimeout: options.UDPTimeout,
|
||||
broadcastAddr: BroadcastAddr(options.TunOptions.Inet4Address),
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
}
|
||||
@@ -71,6 +70,7 @@ func (t *GVisor) Start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
linkEndpoint = &LinkEndpointFilter{linkEndpoint, t.broadcastAddr, t.tun}
|
||||
ipStack, err := newGVisorStack(linkEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -122,19 +122,19 @@ func (t *GVisor) Start() error {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint)
|
||||
udpConn := gonet.NewUDPConn(&wq, endpoint)
|
||||
lAddr := udpConn.RemoteAddr()
|
||||
rAddr := udpConn.LocalAddr()
|
||||
if lAddr == nil || rAddr == nil {
|
||||
endpoint.Abort()
|
||||
return
|
||||
}
|
||||
gConn := &gUDPConn{UDPConn: udpConn, stack: ipStack, packet: (*gRequest)(unsafe.Pointer(request)).pkt.IncRef()}
|
||||
gConn := &gUDPConn{UDPConn: udpConn}
|
||||
go func() {
|
||||
var metadata M.Metadata
|
||||
metadata.Source = M.SocksaddrFromNet(lAddr)
|
||||
metadata.Destination = M.SocksaddrFromNet(rAddr)
|
||||
ctx, conn := canceler.NewPacketConn(t.ctx, bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(gConn), Addr: M.SocksaddrFromNet(rAddr)}), time.Duration(t.udpTimeout)*time.Second)
|
||||
ctx, conn := canceler.NewPacketConn(t.ctx, bufio.NewUnbindPacketConnWithAddr(gConn, metadata.Destination), time.Duration(t.udpTimeout)*time.Second)
|
||||
hErr := t.handler.NewPacketConnection(ctx, conn, metadata)
|
||||
if hErr != nil {
|
||||
endpoint.Abort()
|
||||
|
||||
56
stack_gvisor_filter.go
Normal file
56
stack_gvisor_filter.go
Normal file
@@ -0,0 +1,56 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ stack.LinkEndpoint = (*LinkEndpointFilter)(nil)
|
||||
|
||||
type LinkEndpointFilter struct {
|
||||
stack.LinkEndpoint
|
||||
BroadcastAddress netip.Addr
|
||||
Writer N.VectorisedWriter
|
||||
}
|
||||
|
||||
func (w *LinkEndpointFilter) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
w.LinkEndpoint.Attach(&networkDispatcherFilter{dispatcher, w.BroadcastAddress, w.Writer})
|
||||
}
|
||||
|
||||
var _ stack.NetworkDispatcher = (*networkDispatcherFilter)(nil)
|
||||
|
||||
type networkDispatcherFilter struct {
|
||||
stack.NetworkDispatcher
|
||||
broadcastAddress netip.Addr
|
||||
writer N.VectorisedWriter
|
||||
}
|
||||
|
||||
func (w *networkDispatcherFilter) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
var network header.Network
|
||||
if protocol == header.IPv4ProtocolNumber {
|
||||
if headerPackets, loaded := pkt.Data().PullUp(header.IPv4MinimumSize); loaded {
|
||||
network = header.IPv4(headerPackets)
|
||||
}
|
||||
} else {
|
||||
if headerPackets, loaded := pkt.Data().PullUp(header.IPv6MinimumSize); loaded {
|
||||
network = header.IPv6(headerPackets)
|
||||
}
|
||||
}
|
||||
if network == nil {
|
||||
w.NetworkDispatcher.DeliverNetworkPacket(protocol, pkt)
|
||||
return
|
||||
}
|
||||
destination := AddrFromAddress(network.DestinationAddress())
|
||||
if destination == w.broadcastAddress || !destination.IsGlobalUnicast() {
|
||||
_, _ = bufio.WriteVectorised(w.writer, pkt.AsSlices())
|
||||
return
|
||||
}
|
||||
w.NetworkDispatcher.DeliverNetworkPacket(protocol, pkt)
|
||||
}
|
||||
@@ -30,9 +30,8 @@ type UDPForwarder struct {
|
||||
udpNat *udpnat.Service[netip.AddrPort]
|
||||
|
||||
// cache
|
||||
cacheProto tcpip.NetworkProtocolNumber
|
||||
cacheID stack.TransportEndpointID
|
||||
cachePacket stack.PacketBufferPtr
|
||||
cacheProto tcpip.NetworkProtocolNumber
|
||||
cacheID stack.TransportEndpointID
|
||||
}
|
||||
|
||||
func NewUDPForwarder(ctx context.Context, stack *stack.Stack, handler Handler, udpTimeout int64) *UDPForwarder {
|
||||
@@ -43,7 +42,7 @@ func NewUDPForwarder(ctx context.Context, stack *stack.Stack, handler Handler, u
|
||||
}
|
||||
}
|
||||
|
||||
func (f *UDPForwarder) HandlePacket(id stack.TransportEndpointID, pkt stack.PacketBufferPtr) bool {
|
||||
func (f *UDPForwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||
var upstreamMetadata M.Metadata
|
||||
upstreamMetadata.Source = M.SocksaddrFrom(AddrFromAddress(id.RemoteAddress), id.RemotePort)
|
||||
upstreamMetadata.Destination = M.SocksaddrFrom(AddrFromAddress(id.LocalAddress), id.LocalPort)
|
||||
@@ -58,7 +57,6 @@ func (f *UDPForwarder) HandlePacket(id stack.TransportEndpointID, pkt stack.Pack
|
||||
sBuffer.Write(view.AsSlice())
|
||||
})
|
||||
f.cacheID = id
|
||||
f.cachePacket = pkt
|
||||
f.udpNat.NewPacket(
|
||||
f.ctx,
|
||||
upstreamMetadata.Source.AddrPort(),
|
||||
@@ -75,7 +73,6 @@ func (f *UDPForwarder) newUDPConn(natConn N.PacketConn) N.PacketWriter {
|
||||
source: f.cacheID.RemoteAddress,
|
||||
sourcePort: f.cacheID.RemotePort,
|
||||
sourceNetwork: f.cacheProto,
|
||||
packet: f.cachePacket.IncRef(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +82,6 @@ type UDPBackWriter struct {
|
||||
source tcpip.Address
|
||||
sourcePort uint16
|
||||
sourceNetwork tcpip.NetworkProtocolNumber
|
||||
packet stack.PacketBufferPtr
|
||||
}
|
||||
|
||||
func (w *UDPBackWriter) Close() error {
|
||||
w.access.Lock()
|
||||
defer w.access.Unlock()
|
||||
if w.packet == nil {
|
||||
return os.ErrClosed
|
||||
}
|
||||
w.packet.DecRef()
|
||||
w.packet = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *UDPBackWriter) WritePacket(packetBuffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
@@ -104,7 +89,7 @@ func (w *UDPBackWriter) WritePacket(packetBuffer *buf.Buffer, destination M.Sock
|
||||
return E.Cause(os.ErrInvalid, "invalid destination")
|
||||
} else if destination.IsIPv4() && w.sourceNetwork == header.IPv6ProtocolNumber {
|
||||
destination = M.SocksaddrFrom(netip.AddrFrom16(destination.Addr.As16()), destination.Port)
|
||||
} else if destination.IsIPv6() && (w.sourceNetwork == header.IPv4AddressSizeBits) {
|
||||
} else if destination.IsIPv6() && (w.sourceNetwork == header.IPv4ProtocolNumber) {
|
||||
return E.New("send IPv6 packet to IPv4 connection")
|
||||
}
|
||||
|
||||
@@ -163,27 +148,8 @@ func (w *UDPBackWriter) WritePacket(packetBuffer *buf.Buffer, destination M.Sock
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *UDPBackWriter) HandshakeFailure(err error) error {
|
||||
if w.packet == nil {
|
||||
return os.ErrClosed
|
||||
}
|
||||
err = gWriteUnreachable(w.stack, w.packet, err)
|
||||
w.packet.DecRef()
|
||||
w.packet = nil
|
||||
return err
|
||||
}
|
||||
|
||||
type gRequest struct {
|
||||
stack *stack.Stack
|
||||
id stack.TransportEndpointID
|
||||
pkt stack.PacketBufferPtr
|
||||
}
|
||||
|
||||
type gUDPConn struct {
|
||||
*gonet.UDPConn
|
||||
access sync.Mutex
|
||||
stack *stack.Stack
|
||||
packet stack.PacketBufferPtr
|
||||
}
|
||||
|
||||
func (c *gUDPConn) Read(b []byte) (n int, err error) {
|
||||
@@ -205,27 +171,10 @@ func (c *gUDPConn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (c *gUDPConn) Close() error {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
if c.packet == nil {
|
||||
return os.ErrClosed
|
||||
}
|
||||
c.packet.DecRef()
|
||||
c.packet = nil
|
||||
return c.UDPConn.Close()
|
||||
}
|
||||
|
||||
func (c *gUDPConn) HandshakeFailure(err error) error {
|
||||
if c.packet == nil {
|
||||
return os.ErrClosed
|
||||
}
|
||||
err = gWriteUnreachable(c.stack, c.packet, err)
|
||||
c.packet.DecRef()
|
||||
c.packet = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func gWriteUnreachable(gStack *stack.Stack, packet stack.PacketBufferPtr, err error) error {
|
||||
func gWriteUnreachable(gStack *stack.Stack, packet *stack.PacketBuffer, err error) (retErr error) {
|
||||
if errors.Is(err, syscall.ENETUNREACH) {
|
||||
if packet.NetworkProtocolNumber == header.IPv4ProtocolNumber {
|
||||
return gWriteUnreachable4(gStack, packet, stack.RejectIPv4WithICMPNetUnreachable)
|
||||
@@ -248,7 +197,7 @@ func gWriteUnreachable(gStack *stack.Stack, packet stack.PacketBufferPtr, err er
|
||||
return nil
|
||||
}
|
||||
|
||||
func gWriteUnreachable4(gStack *stack.Stack, packet stack.PacketBufferPtr, icmpCode stack.RejectIPv4WithICMPType) error {
|
||||
func gWriteUnreachable4(gStack *stack.Stack, packet *stack.PacketBuffer, icmpCode stack.RejectIPv4WithICMPType) error {
|
||||
err := gStack.NetworkProtocolInstance(header.IPv4ProtocolNumber).(stack.RejectIPv4WithHandler).SendRejectionError(packet, icmpCode, true)
|
||||
if err != nil {
|
||||
return wrapStackError(err)
|
||||
@@ -256,7 +205,7 @@ func gWriteUnreachable4(gStack *stack.Stack, packet stack.PacketBufferPtr, icmpC
|
||||
return nil
|
||||
}
|
||||
|
||||
func gWriteUnreachable6(gStack *stack.Stack, packet stack.PacketBufferPtr, icmpCode stack.RejectIPv6WithICMPType) error {
|
||||
func gWriteUnreachable6(gStack *stack.Stack, packet *stack.PacketBuffer, icmpCode stack.RejectIPv6WithICMPType) error {
|
||||
err := gStack.NetworkProtocolInstance(header.IPv6ProtocolNumber).(stack.RejectIPv6WithHandler).SendRejectionError(packet, icmpCode, true)
|
||||
if err != nil {
|
||||
return wrapStackError(err)
|
||||
|
||||
144
stack_lwip.go
144
stack_lwip.go
@@ -1,144 +0,0 @@
|
||||
//go:build with_lwip
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
lwip "github.com/sagernet/go-tun2socks/core"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/udpnat"
|
||||
)
|
||||
|
||||
type LWIP struct {
|
||||
ctx context.Context
|
||||
tun Tun
|
||||
tunMtu uint32
|
||||
udpTimeout int64
|
||||
handler Handler
|
||||
stack lwip.LWIPStack
|
||||
udpNat *udpnat.Service[netip.AddrPort]
|
||||
}
|
||||
|
||||
func NewLWIP(
|
||||
options StackOptions,
|
||||
) (Stack, error) {
|
||||
return &LWIP{
|
||||
ctx: options.Context,
|
||||
tun: options.Tun,
|
||||
tunMtu: options.MTU,
|
||||
handler: options.Handler,
|
||||
stack: lwip.NewLWIPStack(),
|
||||
udpNat: udpnat.New[netip.AddrPort](options.UDPTimeout, options.Handler),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *LWIP) Start() error {
|
||||
lwip.RegisterTCPConnHandler(l)
|
||||
lwip.RegisterUDPConnHandler(l)
|
||||
lwip.RegisterOutputFn(l.tun.Write)
|
||||
go l.loopIn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LWIP) loopIn() {
|
||||
if winTun, isWintun := l.tun.(WinTun); isWintun {
|
||||
l.loopInWintun(winTun)
|
||||
return
|
||||
}
|
||||
buffer := make([]byte, int(l.tunMtu)+PacketOffset)
|
||||
for {
|
||||
n, err := l.tun.Read(buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = l.stack.Write(buffer[PacketOffset:n])
|
||||
if err != nil {
|
||||
if err.Error() == "stack closed" {
|
||||
return
|
||||
}
|
||||
l.handler.NewError(context.Background(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
lwip.RegisterTCPConnHandler(nil)
|
||||
lwip.RegisterUDPConnHandler(nil)
|
||||
lwip.RegisterOutputFn(func(bytes []byte) (int, error) {
|
||||
return 0, os.ErrClosed
|
||||
})
|
||||
return l.stack.Close()
|
||||
}
|
||||
|
||||
func (l *LWIP) Handle(conn net.Conn) error {
|
||||
lAddr := conn.LocalAddr()
|
||||
rAddr := conn.RemoteAddr()
|
||||
if lAddr == nil || rAddr == nil {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
go func() {
|
||||
var metadata M.Metadata
|
||||
metadata.Source = M.SocksaddrFromNet(lAddr)
|
||||
metadata.Destination = M.SocksaddrFromNet(rAddr)
|
||||
hErr := l.handler.NewConnection(l.ctx, conn, metadata)
|
||||
if hErr != nil {
|
||||
conn.(lwip.TCPConn).Abort()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LWIP) ReceiveTo(conn lwip.UDPConn, data []byte, addr M.Socksaddr) error {
|
||||
var upstreamMetadata M.Metadata
|
||||
upstreamMetadata.Source = conn.LocalAddr()
|
||||
upstreamMetadata.Destination = addr
|
||||
|
||||
l.udpNat.NewPacket(
|
||||
l.ctx,
|
||||
upstreamMetadata.Source.AddrPort(),
|
||||
buf.As(data).ToOwned(),
|
||||
upstreamMetadata,
|
||||
func(natConn N.PacketConn) N.PacketWriter {
|
||||
return &LWIPUDPBackWriter{conn}
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
type LWIPUDPBackWriter struct {
|
||||
conn lwip.UDPConn
|
||||
}
|
||||
|
||||
func (w *LWIPUDPBackWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
defer buffer.Release()
|
||||
return common.Error(w.conn.WriteFrom(buffer.Bytes(), destination))
|
||||
}
|
||||
|
||||
func (w *LWIPUDPBackWriter) Close() error {
|
||||
return w.conn.Close()
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !with_lwip
|
||||
|
||||
package tun
|
||||
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
func NewLWIP(
|
||||
options StackOptions,
|
||||
) (Stack, error) {
|
||||
return nil, E.New(`LWIP is not included in this build, rebuild with -tags with_lwip`)
|
||||
}
|
||||
173
stack_mixed.go
173
stack_mixed.go
@@ -4,7 +4,6 @@ package tun
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
||||
@@ -14,17 +13,14 @@ import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||
"github.com/sagernet/gvisor/pkg/waiter"
|
||||
"github.com/sagernet/sing-tun/internal/clashtcpip"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/canceler"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Mixed struct {
|
||||
*System
|
||||
writer N.VectorisedWriter
|
||||
endpointIndependentNat bool
|
||||
stack *stack.Stack
|
||||
endpoint *channel.Endpoint
|
||||
@@ -39,7 +35,6 @@ func NewMixed(
|
||||
}
|
||||
return &Mixed{
|
||||
System: system.(*System),
|
||||
writer: options.Tun.CreateVectorisedWriter(),
|
||||
endpointIndependentNat: options.EndpointIndependentNat,
|
||||
}, nil
|
||||
}
|
||||
@@ -49,7 +44,7 @@ func (m *Mixed) Start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint := channel.New(1024, m.mtu, "")
|
||||
endpoint := channel.New(1024, uint32(m.mtu), "")
|
||||
ipStack, err := newGVisorStack(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -61,19 +56,19 @@ func (m *Mixed) Start() error {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint)
|
||||
udpConn := gonet.NewUDPConn(&wq, endpoint)
|
||||
lAddr := udpConn.RemoteAddr()
|
||||
rAddr := udpConn.LocalAddr()
|
||||
if lAddr == nil || rAddr == nil {
|
||||
endpoint.Abort()
|
||||
return
|
||||
}
|
||||
gConn := &gUDPConn{UDPConn: udpConn, stack: ipStack, packet: (*gRequest)(unsafe.Pointer(request)).pkt.IncRef()}
|
||||
gConn := &gUDPConn{UDPConn: udpConn}
|
||||
go func() {
|
||||
var metadata M.Metadata
|
||||
metadata.Source = M.SocksaddrFromNet(lAddr)
|
||||
metadata.Destination = M.SocksaddrFromNet(rAddr)
|
||||
ctx, conn := canceler.NewPacketConn(m.ctx, bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(gConn), Addr: M.SocksaddrFromNet(rAddr)}), time.Duration(m.udpTimeout)*time.Second)
|
||||
ctx, conn := canceler.NewPacketConn(m.ctx, bufio.NewUnbindPacketConnWithAddr(gConn, metadata.Destination), time.Duration(m.udpTimeout)*time.Second)
|
||||
hErr := m.handler.NewPacketConnection(ctx, conn, metadata)
|
||||
if hErr != nil {
|
||||
endpoint.Abort()
|
||||
@@ -96,26 +91,34 @@ func (m *Mixed) tunLoop() {
|
||||
m.wintunLoop(winTun)
|
||||
return
|
||||
}
|
||||
if linuxTUN, isLinuxTUN := m.tun.(LinuxTUN); isLinuxTUN {
|
||||
m.frontHeadroom = linuxTUN.FrontHeadroom()
|
||||
m.txChecksumOffload = linuxTUN.TXChecksumOffload()
|
||||
batchSize := linuxTUN.BatchSize()
|
||||
if batchSize > 1 {
|
||||
m.batchLoop(linuxTUN, batchSize)
|
||||
return
|
||||
}
|
||||
}
|
||||
packetBuffer := make([]byte, m.mtu+PacketOffset)
|
||||
for {
|
||||
n, err := m.tun.Read(packetBuffer)
|
||||
if err != nil {
|
||||
return
|
||||
if E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
m.logger.Error(E.Cause(err, "read packet"))
|
||||
}
|
||||
if n < clashtcpip.IPv4PacketMinLength {
|
||||
continue
|
||||
}
|
||||
rawPacket := packetBuffer[:n]
|
||||
packet := packetBuffer[PacketOffset:n]
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
err = m.processIPv4(packet)
|
||||
case 6:
|
||||
err = m.processIPv6(packet)
|
||||
default:
|
||||
err = E.New("ip: unknown version: ", ipVersion)
|
||||
}
|
||||
if err != nil {
|
||||
m.logger.Trace(err)
|
||||
if m.processPacket(packet) {
|
||||
_, err = m.tun.Write(rawPacket)
|
||||
if err != nil {
|
||||
m.logger.Trace(E.Cause(err, "write packet"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,55 +133,119 @@ func (m *Mixed) wintunLoop(winTun WinTun) {
|
||||
release()
|
||||
continue
|
||||
}
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
err = m.processIPv4(packet)
|
||||
case 6:
|
||||
err = m.processIPv6(packet)
|
||||
default:
|
||||
err = E.New("ip: unknown version: ", ipVersion)
|
||||
}
|
||||
if err != nil {
|
||||
m.logger.Trace(err)
|
||||
if m.processPacket(packet) {
|
||||
_, err = winTun.Write(packet)
|
||||
if err != nil {
|
||||
m.logger.Trace(E.Cause(err, "write packet"))
|
||||
}
|
||||
}
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mixed) processIPv4(packet clashtcpip.IPv4Packet) error {
|
||||
switch packet.Protocol() {
|
||||
case clashtcpip.TCP:
|
||||
return m.processIPv4TCP(packet, packet.Payload())
|
||||
case clashtcpip.UDP:
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithData(packet),
|
||||
})
|
||||
m.endpoint.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
||||
pkt.DecRef()
|
||||
return nil
|
||||
case clashtcpip.ICMP:
|
||||
return m.processIPv4ICMP(packet, packet.Payload())
|
||||
default:
|
||||
return common.Error(m.tun.Write(packet))
|
||||
func (m *Mixed) batchLoop(linuxTUN LinuxTUN, batchSize int) {
|
||||
packetBuffers := make([][]byte, batchSize)
|
||||
writeBuffers := make([][]byte, batchSize)
|
||||
packetSizes := make([]int, batchSize)
|
||||
for i := range packetBuffers {
|
||||
packetBuffers[i] = make([]byte, m.mtu+m.frontHeadroom)
|
||||
}
|
||||
for {
|
||||
n, err := linuxTUN.BatchRead(packetBuffers, m.frontHeadroom, packetSizes)
|
||||
if err != nil {
|
||||
if E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
m.logger.Error(E.Cause(err, "batch read packet"))
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
packetSize := packetSizes[i]
|
||||
if packetSize < clashtcpip.IPv4PacketMinLength {
|
||||
continue
|
||||
}
|
||||
packetBuffer := packetBuffers[i]
|
||||
packet := packetBuffer[m.frontHeadroom : m.frontHeadroom+packetSize]
|
||||
if m.processPacket(packet) {
|
||||
writeBuffers = append(writeBuffers, packetBuffer[:m.frontHeadroom+packetSize])
|
||||
}
|
||||
}
|
||||
if len(writeBuffers) > 0 {
|
||||
err = linuxTUN.BatchWrite(writeBuffers, m.frontHeadroom)
|
||||
if err != nil {
|
||||
m.logger.Trace(E.Cause(err, "batch write packet"))
|
||||
}
|
||||
writeBuffers = writeBuffers[:0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mixed) processIPv6(packet clashtcpip.IPv6Packet) error {
|
||||
func (m *Mixed) processPacket(packet []byte) bool {
|
||||
var (
|
||||
writeBack bool
|
||||
err error
|
||||
)
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
writeBack, err = m.processIPv4(packet)
|
||||
case 6:
|
||||
writeBack, err = m.processIPv6(packet)
|
||||
default:
|
||||
err = E.New("ip: unknown version: ", ipVersion)
|
||||
}
|
||||
if err != nil {
|
||||
m.logger.Trace(err)
|
||||
return false
|
||||
}
|
||||
return writeBack
|
||||
}
|
||||
|
||||
func (m *Mixed) processIPv4(packet clashtcpip.IPv4Packet) (writeBack bool, err error) {
|
||||
writeBack = true
|
||||
destination := packet.DestinationIP()
|
||||
if destination == m.broadcastAddr || !destination.IsGlobalUnicast() {
|
||||
return
|
||||
}
|
||||
switch packet.Protocol() {
|
||||
case clashtcpip.TCP:
|
||||
return m.processIPv6TCP(packet, packet.Payload())
|
||||
err = m.processIPv4TCP(packet, packet.Payload())
|
||||
case clashtcpip.UDP:
|
||||
writeBack = false
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithData(packet),
|
||||
Payload: buffer.MakeWithData(packet),
|
||||
IsForwardedPacket: true,
|
||||
})
|
||||
m.endpoint.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
||||
pkt.DecRef()
|
||||
return
|
||||
case clashtcpip.ICMP:
|
||||
err = m.processIPv4ICMP(packet, packet.Payload())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Mixed) processIPv6(packet clashtcpip.IPv6Packet) (writeBack bool, err error) {
|
||||
writeBack = true
|
||||
if !packet.DestinationIP().IsGlobalUnicast() {
|
||||
return
|
||||
}
|
||||
switch packet.Protocol() {
|
||||
case clashtcpip.TCP:
|
||||
err = m.processIPv6TCP(packet, packet.Payload())
|
||||
case clashtcpip.UDP:
|
||||
writeBack = false
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithData(packet),
|
||||
IsForwardedPacket: true,
|
||||
})
|
||||
m.endpoint.InjectInbound(header.IPv6ProtocolNumber, pkt)
|
||||
pkt.DecRef()
|
||||
return nil
|
||||
case clashtcpip.ICMPv6:
|
||||
return m.processIPv6ICMP(packet, packet.Payload())
|
||||
default:
|
||||
return common.Error(m.tun.Write(packet))
|
||||
err = m.processIPv6ICMP(packet, packet.Payload())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Mixed) packetLoop() {
|
||||
@@ -187,7 +254,7 @@ func (m *Mixed) packetLoop() {
|
||||
if packet == nil {
|
||||
break
|
||||
}
|
||||
bufio.WriteVectorised(m.writer, packet.AsSlices())
|
||||
bufio.WriteVectorised(m.tun, packet.AsSlices())
|
||||
packet.DecRef()
|
||||
}
|
||||
}
|
||||
|
||||
356
stack_system.go
356
stack_system.go
@@ -18,11 +18,13 @@ import (
|
||||
"github.com/sagernet/sing/common/udpnat"
|
||||
)
|
||||
|
||||
var ErrIncludeAllNetworks = E.New("`system` and `mixed` stack are not available when `includeAllNetworks` is enabled. See https://github.com/SagerNet/sing-tun/issues/25")
|
||||
|
||||
type System struct {
|
||||
ctx context.Context
|
||||
tun Tun
|
||||
tunName string
|
||||
mtu uint32
|
||||
mtu int
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
inet4Prefixes []netip.Prefix
|
||||
@@ -31,6 +33,7 @@ type System struct {
|
||||
inet4Address netip.Addr
|
||||
inet6ServerAddress netip.Addr
|
||||
inet6Address netip.Addr
|
||||
broadcastAddr netip.Addr
|
||||
udpTimeout int64
|
||||
tcpListener net.Listener
|
||||
tcpListener6 net.Listener
|
||||
@@ -40,6 +43,8 @@ type System struct {
|
||||
udpNat *udpnat.Service[netip.AddrPort]
|
||||
bindInterface bool
|
||||
interfaceFinder control.InterfaceFinder
|
||||
frontHeadroom int
|
||||
txChecksumOffload bool
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
@@ -53,28 +58,29 @@ func NewSystem(options StackOptions) (Stack, error) {
|
||||
stack := &System{
|
||||
ctx: options.Context,
|
||||
tun: options.Tun,
|
||||
tunName: options.Name,
|
||||
mtu: options.MTU,
|
||||
tunName: options.TunOptions.Name,
|
||||
mtu: int(options.TunOptions.MTU),
|
||||
udpTimeout: options.UDPTimeout,
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
inet4Prefixes: options.Inet4Address,
|
||||
inet6Prefixes: options.Inet6Address,
|
||||
inet4Prefixes: options.TunOptions.Inet4Address,
|
||||
inet6Prefixes: options.TunOptions.Inet6Address,
|
||||
broadcastAddr: BroadcastAddr(options.TunOptions.Inet4Address),
|
||||
bindInterface: options.ForwarderBindInterface,
|
||||
interfaceFinder: options.InterfaceFinder,
|
||||
}
|
||||
if len(options.Inet4Address) > 0 {
|
||||
if options.Inet4Address[0].Bits() == 32 {
|
||||
if len(options.TunOptions.Inet4Address) > 0 {
|
||||
if options.TunOptions.Inet4Address[0].Bits() == 32 {
|
||||
return nil, E.New("need one more IPv4 address in first prefix for system stack")
|
||||
}
|
||||
stack.inet4ServerAddress = options.Inet4Address[0].Addr()
|
||||
stack.inet4ServerAddress = options.TunOptions.Inet4Address[0].Addr()
|
||||
stack.inet4Address = stack.inet4ServerAddress.Next()
|
||||
}
|
||||
if len(options.Inet6Address) > 0 {
|
||||
if options.Inet6Address[0].Bits() == 128 {
|
||||
if len(options.TunOptions.Inet6Address) > 0 {
|
||||
if options.TunOptions.Inet6Address[0].Bits() == 128 {
|
||||
return nil, E.New("need one more IPv6 address in first prefix for system stack")
|
||||
}
|
||||
stack.inet6ServerAddress = options.Inet6Address[0].Addr()
|
||||
stack.inet6ServerAddress = options.TunOptions.Inet6Address[0].Addr()
|
||||
stack.inet6Address = stack.inet6ServerAddress.Next()
|
||||
}
|
||||
if !stack.inet4Address.IsValid() && !stack.inet6Address.IsValid() {
|
||||
@@ -107,9 +113,9 @@ 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)
|
||||
bindErr := control.BindToInterface0(s.interfaceFinder, conn, network, address, s.tunName, -1, true)
|
||||
if bindErr != nil {
|
||||
s.logger.Warn("bind forwarder to interface: ", bindErr)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -142,26 +148,34 @@ func (s *System) tunLoop() {
|
||||
s.wintunLoop(winTun)
|
||||
return
|
||||
}
|
||||
if linuxTUN, isLinuxTUN := s.tun.(LinuxTUN); isLinuxTUN {
|
||||
s.frontHeadroom = linuxTUN.FrontHeadroom()
|
||||
s.txChecksumOffload = linuxTUN.TXChecksumOffload()
|
||||
batchSize := linuxTUN.BatchSize()
|
||||
if batchSize > 1 {
|
||||
s.batchLoop(linuxTUN, batchSize)
|
||||
return
|
||||
}
|
||||
}
|
||||
packetBuffer := make([]byte, s.mtu+PacketOffset)
|
||||
for {
|
||||
n, err := s.tun.Read(packetBuffer)
|
||||
if err != nil {
|
||||
return
|
||||
if E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
s.logger.Error(E.Cause(err, "read packet"))
|
||||
}
|
||||
if n < clashtcpip.IPv4PacketMinLength {
|
||||
continue
|
||||
}
|
||||
rawPacket := packetBuffer[:n]
|
||||
packet := packetBuffer[PacketOffset:n]
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
err = s.processIPv4(packet)
|
||||
case 6:
|
||||
err = s.processIPv6(packet)
|
||||
default:
|
||||
err = E.New("ip: unknown version: ", ipVersion)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Trace(err)
|
||||
if s.processPacket(packet) {
|
||||
_, err = s.tun.Write(rawPacket)
|
||||
if err != nil {
|
||||
s.logger.Trace(E.Cause(err, "write packet"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,21 +190,75 @@ func (s *System) wintunLoop(winTun WinTun) {
|
||||
release()
|
||||
continue
|
||||
}
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
err = s.processIPv4(packet)
|
||||
case 6:
|
||||
err = s.processIPv6(packet)
|
||||
default:
|
||||
err = E.New("ip: unknown version: ", ipVersion)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Trace(err)
|
||||
if s.processPacket(packet) {
|
||||
_, err = winTun.Write(packet)
|
||||
if err != nil {
|
||||
s.logger.Trace(E.Cause(err, "write packet"))
|
||||
}
|
||||
}
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) batchLoop(linuxTUN LinuxTUN, batchSize int) {
|
||||
packetBuffers := make([][]byte, batchSize)
|
||||
writeBuffers := make([][]byte, batchSize)
|
||||
packetSizes := make([]int, batchSize)
|
||||
for i := range packetBuffers {
|
||||
packetBuffers[i] = make([]byte, s.mtu+s.frontHeadroom)
|
||||
}
|
||||
for {
|
||||
n, err := linuxTUN.BatchRead(packetBuffers, s.frontHeadroom, packetSizes)
|
||||
if err != nil {
|
||||
if E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
s.logger.Error(E.Cause(err, "batch read packet"))
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
packetSize := packetSizes[i]
|
||||
if packetSize < clashtcpip.IPv4PacketMinLength {
|
||||
continue
|
||||
}
|
||||
packetBuffer := packetBuffers[i]
|
||||
packet := packetBuffer[s.frontHeadroom : s.frontHeadroom+packetSize]
|
||||
if s.processPacket(packet) {
|
||||
writeBuffers = append(writeBuffers, packetBuffer[:s.frontHeadroom+packetSize])
|
||||
}
|
||||
}
|
||||
if len(writeBuffers) > 0 {
|
||||
err = linuxTUN.BatchWrite(writeBuffers, s.frontHeadroom)
|
||||
if err != nil {
|
||||
s.logger.Trace(E.Cause(err, "batch write packet"))
|
||||
}
|
||||
writeBuffers = writeBuffers[:0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) processPacket(packet []byte) bool {
|
||||
var (
|
||||
writeBack bool
|
||||
err error
|
||||
)
|
||||
switch ipVersion := packet[0] >> 4; ipVersion {
|
||||
case 4:
|
||||
writeBack, err = s.processIPv4(packet)
|
||||
case 6:
|
||||
writeBack, err = s.processIPv6(packet)
|
||||
default:
|
||||
err = E.New("ip: unknown version: ", ipVersion)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Trace(err)
|
||||
return false
|
||||
}
|
||||
return writeBack
|
||||
}
|
||||
|
||||
func (s *System) acceptLoop(listener net.Listener) {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
@@ -232,37 +300,46 @@ func (s *System) acceptLoop(listener net.Listener) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) processIPv4(packet clashtcpip.IPv4Packet) error {
|
||||
func (s *System) processIPv4(packet clashtcpip.IPv4Packet) (writeBack bool, err error) {
|
||||
writeBack = true
|
||||
destination := packet.DestinationIP()
|
||||
if destination == s.broadcastAddr || !destination.IsGlobalUnicast() {
|
||||
return
|
||||
}
|
||||
switch packet.Protocol() {
|
||||
case clashtcpip.TCP:
|
||||
return s.processIPv4TCP(packet, packet.Payload())
|
||||
err = s.processIPv4TCP(packet, packet.Payload())
|
||||
case clashtcpip.UDP:
|
||||
return s.processIPv4UDP(packet, packet.Payload())
|
||||
writeBack = false
|
||||
err = s.processIPv4UDP(packet, packet.Payload())
|
||||
case clashtcpip.ICMP:
|
||||
return s.processIPv4ICMP(packet, packet.Payload())
|
||||
default:
|
||||
return common.Error(s.tun.Write(packet))
|
||||
err = s.processIPv4ICMP(packet, packet.Payload())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *System) processIPv6(packet clashtcpip.IPv6Packet) error {
|
||||
func (s *System) processIPv6(packet clashtcpip.IPv6Packet) (writeBack bool, err error) {
|
||||
writeBack = true
|
||||
if !packet.DestinationIP().IsGlobalUnicast() {
|
||||
return
|
||||
}
|
||||
switch packet.Protocol() {
|
||||
case clashtcpip.TCP:
|
||||
return s.processIPv6TCP(packet, packet.Payload())
|
||||
err = s.processIPv6TCP(packet, packet.Payload())
|
||||
case clashtcpip.UDP:
|
||||
return s.processIPv6UDP(packet, packet.Payload())
|
||||
writeBack = false
|
||||
err = s.processIPv6UDP(packet, packet.Payload())
|
||||
case clashtcpip.ICMPv6:
|
||||
return s.processIPv6ICMP(packet, packet.Payload())
|
||||
default:
|
||||
return common.Error(s.tun.Write(packet))
|
||||
err = s.processIPv6ICMP(packet, packet.Payload())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *System) processIPv4TCP(packet clashtcpip.IPv4Packet, header clashtcpip.TCPPacket) error {
|
||||
source := netip.AddrPortFrom(packet.SourceIP(), header.SourcePort())
|
||||
destination := netip.AddrPortFrom(packet.DestinationIP(), header.DestinationPort())
|
||||
if !destination.Addr().IsGlobalUnicast() {
|
||||
return common.Error(s.tun.Write(packet))
|
||||
return nil
|
||||
} else if source.Addr() == s.inet4ServerAddress && source.Port() == s.tcpPort {
|
||||
session := s.tcpNat.LookupBack(destination.Port())
|
||||
if session == nil {
|
||||
@@ -279,16 +356,21 @@ func (s *System) processIPv4TCP(packet clashtcpip.IPv4Packet, header clashtcpip.
|
||||
packet.SetDestinationIP(s.inet4ServerAddress)
|
||||
header.SetDestinationPort(s.tcpPort)
|
||||
}
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(s.tun.Write(packet))
|
||||
if !s.txChecksumOffload {
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
} else {
|
||||
header.OffloadChecksum()
|
||||
packet.ResetChecksum()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *System) processIPv6TCP(packet clashtcpip.IPv6Packet, header clashtcpip.TCPPacket) error {
|
||||
source := netip.AddrPortFrom(packet.SourceIP(), header.SourcePort())
|
||||
destination := netip.AddrPortFrom(packet.DestinationIP(), header.DestinationPort())
|
||||
if !destination.Addr().IsGlobalUnicast() {
|
||||
return common.Error(s.tun.Write(packet))
|
||||
return nil
|
||||
} else if source.Addr() == s.inet6ServerAddress && source.Port() == s.tcpPort6 {
|
||||
session := s.tcpNat.LookupBack(destination.Port())
|
||||
if session == nil {
|
||||
@@ -305,9 +387,12 @@ func (s *System) processIPv6TCP(packet clashtcpip.IPv6Packet, header clashtcpip.
|
||||
packet.SetDestinationIP(s.inet6ServerAddress)
|
||||
header.SetDestinationPort(s.tcpPort6)
|
||||
}
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(s.tun.Write(packet))
|
||||
if !s.txChecksumOffload {
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
} else {
|
||||
header.OffloadChecksum()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip.UDPPacket) error {
|
||||
@@ -317,10 +402,13 @@ func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip.
|
||||
if packet.FragmentOffset() != 0 {
|
||||
return E.New("ipv4: udp: fragment dropped")
|
||||
}
|
||||
if !header.Valid() {
|
||||
return E.New("ipv4: udp: invalid packet")
|
||||
}
|
||||
source := netip.AddrPortFrom(packet.SourceIP(), header.SourcePort())
|
||||
destination := netip.AddrPortFrom(packet.DestinationIP(), header.DestinationPort())
|
||||
if !destination.Addr().IsGlobalUnicast() {
|
||||
return common.Error(s.tun.Write(packet))
|
||||
return nil
|
||||
}
|
||||
data := buf.As(header.Payload())
|
||||
if data.Len() == 0 {
|
||||
@@ -334,16 +422,25 @@ func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip.
|
||||
headerLen := packet.HeaderLen() + clashtcpip.UDPHeaderSize
|
||||
headerCopy := make([]byte, headerLen)
|
||||
copy(headerCopy, packet[:headerLen])
|
||||
return &systemUDPPacketWriter4{s.tun, headerCopy, source}
|
||||
return &systemUDPPacketWriter4{
|
||||
s.tun,
|
||||
s.frontHeadroom + PacketOffset,
|
||||
headerCopy,
|
||||
source,
|
||||
s.txChecksumOffload,
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *System) processIPv6UDP(packet clashtcpip.IPv6Packet, header clashtcpip.UDPPacket) error {
|
||||
if !header.Valid() {
|
||||
return E.New("ipv6: udp: invalid packet")
|
||||
}
|
||||
source := netip.AddrPortFrom(packet.SourceIP(), header.SourcePort())
|
||||
destination := netip.AddrPortFrom(packet.DestinationIP(), header.DestinationPort())
|
||||
if !destination.Addr().IsGlobalUnicast() {
|
||||
return common.Error(s.tun.Write(packet))
|
||||
return nil
|
||||
}
|
||||
data := buf.As(header.Payload())
|
||||
if data.Len() == 0 {
|
||||
@@ -357,7 +454,13 @@ 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 &systemUDPPacketWriter6{s.tun, headerCopy, source}
|
||||
return &systemUDPPacketWriter6{
|
||||
s.tun,
|
||||
s.frontHeadroom + PacketOffset,
|
||||
headerCopy,
|
||||
source,
|
||||
s.txChecksumOffload,
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -372,7 +475,7 @@ func (s *System) processIPv4ICMP(packet clashtcpip.IPv4Packet, header clashtcpip
|
||||
packet.SetDestinationIP(sourceAddress)
|
||||
header.ResetChecksum()
|
||||
packet.ResetChecksum()
|
||||
return common.Error(s.tun.Write(packet))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *System) processIPv6ICMP(packet clashtcpip.IPv6Packet, header clashtcpip.ICMPv6Packet) error {
|
||||
@@ -385,102 +488,21 @@ func (s *System) processIPv6ICMP(packet clashtcpip.IPv6Packet, header clashtcpip
|
||||
packet.SetDestinationIP(sourceAddress)
|
||||
header.ResetChecksum(packet.PseudoSum())
|
||||
packet.ResetChecksum()
|
||||
return common.Error(s.tun.Write(packet))
|
||||
}
|
||||
|
||||
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))
|
||||
return nil
|
||||
}
|
||||
|
||||
type systemUDPPacketWriter4 struct {
|
||||
tun Tun
|
||||
header []byte
|
||||
source netip.AddrPort
|
||||
tun Tun
|
||||
frontHeadroom int
|
||||
header []byte
|
||||
source netip.AddrPort
|
||||
txChecksumOffload bool
|
||||
}
|
||||
|
||||
func (w *systemUDPPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
newPacket := buf.NewSize(len(w.header) + buffer.Len())
|
||||
newPacket := buf.NewSize(w.frontHeadroom + len(w.header) + buffer.Len())
|
||||
defer newPacket.Release()
|
||||
newPacket.Resize(w.frontHeadroom, 0)
|
||||
newPacket.Write(w.header)
|
||||
newPacket.Write(buffer.Bytes())
|
||||
ipHdr := clashtcpip.IPv4Packet(newPacket.Bytes())
|
||||
@@ -491,20 +513,33 @@ func (w *systemUDPPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.S
|
||||
udpHdr.SetDestinationPort(udpHdr.SourcePort())
|
||||
udpHdr.SetSourcePort(destination.Port)
|
||||
udpHdr.SetLength(uint16(buffer.Len() + clashtcpip.UDPHeaderSize))
|
||||
udpHdr.ResetChecksum(ipHdr.PseudoSum())
|
||||
ipHdr.ResetChecksum()
|
||||
if !w.txChecksumOffload {
|
||||
udpHdr.ResetChecksum(ipHdr.PseudoSum())
|
||||
ipHdr.ResetChecksum()
|
||||
} else {
|
||||
udpHdr.OffloadChecksum()
|
||||
ipHdr.ResetChecksum()
|
||||
}
|
||||
if PacketOffset > 0 {
|
||||
newPacket.ExtendHeader(PacketOffset)[3] = syscall.AF_INET
|
||||
} else {
|
||||
newPacket.Advance(-w.frontHeadroom)
|
||||
}
|
||||
return common.Error(w.tun.Write(newPacket.Bytes()))
|
||||
}
|
||||
|
||||
type systemUDPPacketWriter6 struct {
|
||||
tun Tun
|
||||
header []byte
|
||||
source netip.AddrPort
|
||||
tun Tun
|
||||
frontHeadroom int
|
||||
header []byte
|
||||
source netip.AddrPort
|
||||
txChecksumOffload bool
|
||||
}
|
||||
|
||||
func (w *systemUDPPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
newPacket := buf.NewSize(len(w.header) + buffer.Len())
|
||||
newPacket := buf.NewSize(w.frontHeadroom + len(w.header) + buffer.Len())
|
||||
defer newPacket.Release()
|
||||
newPacket.Resize(w.frontHeadroom, 0)
|
||||
newPacket.Write(w.header)
|
||||
newPacket.Write(buffer.Bytes())
|
||||
ipHdr := clashtcpip.IPv6Packet(newPacket.Bytes())
|
||||
@@ -516,6 +551,15 @@ func (w *systemUDPPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.S
|
||||
udpHdr.SetDestinationPort(udpHdr.SourcePort())
|
||||
udpHdr.SetSourcePort(destination.Port)
|
||||
udpHdr.SetLength(udpLen)
|
||||
udpHdr.ResetChecksum(ipHdr.PseudoSum())
|
||||
if !w.txChecksumOffload {
|
||||
udpHdr.ResetChecksum(ipHdr.PseudoSum())
|
||||
} else {
|
||||
udpHdr.OffloadChecksum()
|
||||
}
|
||||
if PacketOffset > 0 {
|
||||
newPacket.ExtendHeader(PacketOffset)[3] = syscall.AF_INET6
|
||||
} else {
|
||||
newPacket.Advance(-w.frontHeadroom)
|
||||
}
|
||||
return common.Error(w.tun.Write(newPacket.Bytes()))
|
||||
}
|
||||
|
||||
57
tun.go
57
tun.go
@@ -23,7 +23,7 @@ type Handler interface {
|
||||
|
||||
type Tun interface {
|
||||
io.ReadWriter
|
||||
CreateVectorisedWriter() N.VectorisedWriter
|
||||
N.VectorisedWriter
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -32,26 +32,41 @@ type WinTun interface {
|
||||
ReadPacket() ([]byte, func(), error)
|
||||
}
|
||||
|
||||
type LinuxTUN interface {
|
||||
Tun
|
||||
N.FrontHeadroom
|
||||
BatchSize() int
|
||||
BatchRead(buffers [][]byte, offset int, readN []int) (n int, err error)
|
||||
BatchWrite(buffers [][]byte, offset int) error
|
||||
TXChecksumOffload() bool
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
MTU uint32
|
||||
AutoRoute bool
|
||||
StrictRoute bool
|
||||
Inet4RouteAddress []netip.Prefix
|
||||
Inet6RouteAddress []netip.Prefix
|
||||
IncludeInterface []string
|
||||
ExcludeInterface []string
|
||||
IncludeUID []ranges.Range[uint32]
|
||||
ExcludeUID []ranges.Range[uint32]
|
||||
IncludeAndroidUser []int
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
TableIndex int
|
||||
FileDescriptor int
|
||||
Logger logger.Logger
|
||||
Name string
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
MTU uint32
|
||||
GSO bool
|
||||
AutoRoute bool
|
||||
StrictRoute bool
|
||||
Inet4RouteAddress []netip.Prefix
|
||||
Inet6RouteAddress []netip.Prefix
|
||||
Inet4RouteExcludeAddress []netip.Prefix
|
||||
Inet6RouteExcludeAddress []netip.Prefix
|
||||
IncludeInterface []string
|
||||
ExcludeInterface []string
|
||||
IncludeUID []ranges.Range[uint32]
|
||||
ExcludeUID []ranges.Range[uint32]
|
||||
IncludeAndroidUser []int
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
TableIndex int
|
||||
FileDescriptor int
|
||||
Logger logger.Logger
|
||||
|
||||
// No work for TCP, do not use.
|
||||
_TXChecksumOffload bool
|
||||
}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
@@ -70,7 +85,7 @@ func CalculateInterfaceName(name string) (tunName string) {
|
||||
for _, netInterface := range interfaces {
|
||||
if strings.HasPrefix(netInterface.Name, tunName) {
|
||||
index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)
|
||||
if parseErr == nil {
|
||||
if parseErr == nil && int(index) >= tunIndex {
|
||||
tunIndex = int(index) + 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -68,44 +67,22 @@ func New(options Options) (Tun, error) {
|
||||
if !ok {
|
||||
panic("create vectorised writer")
|
||||
}
|
||||
runtime.SetFinalizer(nativeTun.tunFile, nil)
|
||||
return nativeTun, nil
|
||||
}
|
||||
|
||||
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
||||
/*n, err = t.tunFile.Read(p)
|
||||
if n < 4 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
copy(p[:], p[4:])
|
||||
return n - 4, err*/
|
||||
return t.tunFile.Read(p)
|
||||
}
|
||||
|
||||
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
return t.tunFile.Write(p)
|
||||
}
|
||||
|
||||
var (
|
||||
packetHeader4 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET}
|
||||
packetHeader6 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET6}
|
||||
)
|
||||
|
||||
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
var packetHeader []byte
|
||||
if p[0]>>4 == 4 {
|
||||
packetHeader = packetHeader4[:]
|
||||
} else {
|
||||
packetHeader = packetHeader6[:]
|
||||
}
|
||||
_, err = bufio.WriteVectorised(t.tunWriter, [][]byte{packetHeader, p})
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *NativeTun) CreateVectorisedWriter() N.VectorisedWriter {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
||||
var packetHeader []byte
|
||||
if buffers[0].Byte(0)>>4 == 4 {
|
||||
@@ -263,43 +240,16 @@ func configure(tunFd int, ifIndex int, name string, options Options) error {
|
||||
}
|
||||
}
|
||||
if options.AutoRoute {
|
||||
if len(options.Inet4Address) > 0 {
|
||||
var routes []netip.Prefix
|
||||
if len(options.Inet4RouteAddress) > 0 {
|
||||
routes = append(options.Inet4RouteAddress, netip.PrefixFrom(options.Inet4Address[0].Addr().Next(), 32))
|
||||
var routeRanges []netip.Prefix
|
||||
routeRanges, err = options.BuildAutoRouteRanges(false)
|
||||
for _, routeRange := range routeRanges {
|
||||
if routeRange.Addr().Is4() {
|
||||
err = addRoute(routeRange, options.Inet4Address[0].Addr())
|
||||
} else {
|
||||
routes = []netip.Prefix{
|
||||
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{4, 0, 0, 0}), 6),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{8, 0, 0, 0}), 5),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{16, 0, 0, 0}), 4),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{32, 0, 0, 0}), 3),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{64, 0, 0, 0}), 2),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{128, 0, 0, 0}), 1),
|
||||
}
|
||||
err = addRoute(routeRange, options.Inet6Address[0].Addr())
|
||||
}
|
||||
for _, subnet := range routes {
|
||||
err = addRoute(subnet, options.Inet4Address[0].Addr())
|
||||
if err != nil {
|
||||
return E.Cause(err, "add ipv4 route ", subnet)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(options.Inet6Address) > 0 {
|
||||
var routes []netip.Prefix
|
||||
if len(options.Inet6RouteAddress) > 0 {
|
||||
routes = append(options.Inet6RouteAddress, netip.PrefixFrom(options.Inet6Address[0].Addr().Next(), 128))
|
||||
} else {
|
||||
routes = []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), 3),
|
||||
}
|
||||
}
|
||||
for _, subnet := range routes {
|
||||
err = addRoute(subnet, options.Inet6Address[0].Addr())
|
||||
if err != nil {
|
||||
return E.Cause(err, "add ipv6 route ", subnet)
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause(err, "add route: ", routeRange)
|
||||
}
|
||||
}
|
||||
flushDNSCache()
|
||||
|
||||
@@ -36,7 +36,7 @@ func (e *DarwinEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityNone
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
@@ -51,13 +51,13 @@ func (e *DarwinEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) dispatchLoop() {
|
||||
packetBuffer := make([]byte, e.tun.mtu+4)
|
||||
packetBuffer := make([]byte, e.tun.mtu+PacketOffset)
|
||||
for {
|
||||
n, err := e.tun.tunFile.Read(packetBuffer)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
packet := packetBuffer[4:n]
|
||||
packet := packetBuffer[PacketOffset:n]
|
||||
var networkProtocol tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(packet) {
|
||||
case header.IPv4Version:
|
||||
@@ -102,20 +102,17 @@ func (e *DarwinEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) AddHeader(buffer stack.PacketBufferPtr) {
|
||||
func (e *DarwinEndpoint) AddHeader(buffer *stack.PacketBuffer) {
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||
var n int
|
||||
for _, packet := range packetBufferList.AsSlice() {
|
||||
var packetHeader []byte
|
||||
switch packet.NetworkProtocolNumber {
|
||||
case header.IPv4ProtocolNumber:
|
||||
packetHeader = packetHeader4[:]
|
||||
case header.IPv6ProtocolNumber:
|
||||
packetHeader = packetHeader6[:]
|
||||
}
|
||||
_, err := bufio.WriteVectorised(e.tun.tunWriter, append([][]byte{packetHeader}, packet.AsSlices()...))
|
||||
_, err := bufio.WriteVectorised(e.tun, packet.AsSlices())
|
||||
if err != nil {
|
||||
return n, &tcpip.ErrAborted{}
|
||||
}
|
||||
|
||||
405
tun_linux.go
405
tun_linux.go
@@ -7,11 +7,13 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/netlink"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -22,17 +24,29 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ LinuxTUN = (*NativeTun)(nil)
|
||||
|
||||
type NativeTun struct {
|
||||
tunFd int
|
||||
tunFile *os.File
|
||||
tunWriter N.VectorisedWriter
|
||||
interfaceCallback *list.Element[DefaultInterfaceUpdateCallback]
|
||||
options Options
|
||||
ruleIndex6 []int
|
||||
gsoEnabled bool
|
||||
gsoBuffer []byte
|
||||
gsoToWrite []int
|
||||
gsoReadAccess sync.Mutex
|
||||
tcpGROAccess sync.Mutex
|
||||
tcp4GROTable *tcpGROTable
|
||||
tcp6GROTable *tcpGROTable
|
||||
txChecksumOffload bool
|
||||
}
|
||||
|
||||
func New(options Options) (Tun, error) {
|
||||
var nativeTun *NativeTun
|
||||
if options.FileDescriptor == 0 {
|
||||
tunFd, err := open(options.Name)
|
||||
tunFd, err := open(options.Name, options.GSO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,38 +54,127 @@ func New(options Options) (Tun, error) {
|
||||
if err != nil {
|
||||
return nil, E.Errors(err, unix.Close(tunFd))
|
||||
}
|
||||
nativeTun := &NativeTun{
|
||||
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{
|
||||
nativeTun = &NativeTun{
|
||||
tunFd: options.FileDescriptor,
|
||||
tunFile: os.NewFile(uintptr(options.FileDescriptor), "tun"),
|
||||
options: options,
|
||||
}
|
||||
runtime.SetFinalizer(nativeTun.tunFile, nil)
|
||||
return nativeTun, nil
|
||||
}
|
||||
var ok bool
|
||||
nativeTun.tunWriter, ok = bufio.CreateVectorisedWriter(nativeTun.tunFile)
|
||||
if !ok {
|
||||
panic("create vectorised writer")
|
||||
}
|
||||
return nativeTun, nil
|
||||
}
|
||||
|
||||
func (t *NativeTun) FrontHeadroom() int {
|
||||
if t.gsoEnabled {
|
||||
return virtioNetHdrLen
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
||||
return t.tunFile.Read(p)
|
||||
if t.gsoEnabled {
|
||||
n, err = t.tunFile.Read(t.gsoBuffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var sizes [1]int
|
||||
n, err = handleVirtioRead(t.gsoBuffer[:n], [][]byte{p}, sizes[:], 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
n = sizes[0]
|
||||
return
|
||||
} else {
|
||||
return t.tunFile.Read(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
if t.gsoEnabled {
|
||||
err = t.BatchWrite([][]byte{p}, virtioNetHdrLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
return t.tunFile.Write(p)
|
||||
}
|
||||
|
||||
func (t *NativeTun) CreateVectorisedWriter() N.VectorisedWriter {
|
||||
return bufio.NewVectorisedWriter(t.tunFile)
|
||||
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
||||
if t.gsoEnabled {
|
||||
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.gsoEnabled {
|
||||
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.gsoReadAccess.Lock()
|
||||
defer t.gsoReadAccess.Unlock()
|
||||
n, err = t.tunFile.Read(t.gsoBuffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return handleVirtioRead(t.gsoBuffer[:n], buffers, readN, offset)
|
||||
}
|
||||
|
||||
func (t *NativeTun) BatchWrite(buffers [][]byte, offset int) error {
|
||||
t.tcpGROAccess.Lock()
|
||||
defer func() {
|
||||
t.tcp4GROTable.reset()
|
||||
t.tcp6GROTable.reset()
|
||||
t.tcpGROAccess.Unlock()
|
||||
}()
|
||||
t.gsoToWrite = t.gsoToWrite[:0]
|
||||
err := handleGRO(buffers, offset, t.tcp4GROTable, t.tcp6GROTable, &t.gsoToWrite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset -= virtioNetHdrLen
|
||||
for _, bufferIndex := range t.gsoToWrite {
|
||||
_, err = t.tunFile.Write(buffers[bufferIndex][offset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var controlPath string
|
||||
@@ -86,7 +189,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func open(name string) (int, error) {
|
||||
func open(name string, vnetHdr bool) (int, error) {
|
||||
fd, err := unix.Open(controlPath, unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
@@ -100,6 +203,9 @@ func open(name string) (int, error) {
|
||||
|
||||
copy(ifr.name[:], name)
|
||||
ifr.flags = unix.IFF_TUN | unix.IFF_NO_PI
|
||||
if vnetHdr {
|
||||
ifr.flags |= unix.IFF_VNET_HDR
|
||||
}
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
|
||||
if errno != 0 {
|
||||
unix.Close(fd)
|
||||
@@ -142,6 +248,46 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
|
||||
}
|
||||
}
|
||||
|
||||
if t.options.GSO {
|
||||
var vnetHdrEnabled bool
|
||||
vnetHdrEnabled, err = checkVNETHDREnabled(t.tunFd, t.options.Name)
|
||||
if err != nil {
|
||||
return E.Cause(err, "enable offload: check IFF_VNET_HDR enabled")
|
||||
}
|
||||
if !vnetHdrEnabled {
|
||||
return E.Cause(err, "enable offload: IFF_VNET_HDR not enabled")
|
||||
}
|
||||
err = setTCPOffload(t.tunFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.gsoEnabled = true
|
||||
t.gsoBuffer = make([]byte, virtioNetHdrLen+int(gsoMaxSize))
|
||||
t.tcp4GROTable = newTCPGROTable()
|
||||
t.tcp6GROTable = newTCPGROTable()
|
||||
}
|
||||
|
||||
var rxChecksumOffload bool
|
||||
rxChecksumOffload, err = checkChecksumOffload(t.options.Name, unix.ETHTOOL_GRXCSUM)
|
||||
if err == nil && !rxChecksumOffload {
|
||||
_ = setChecksumOffload(t.options.Name, unix.ETHTOOL_SRXCSUM)
|
||||
}
|
||||
|
||||
if t.options._TXChecksumOffload {
|
||||
var txChecksumOffload bool
|
||||
txChecksumOffload, err = checkChecksumOffload(t.options.Name, unix.ETHTOOL_GTXCSUM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err == nil && !txChecksumOffload {
|
||||
err = setChecksumOffload(t.options.Name, unix.ETHTOOL_STXCSUM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
t.txChecksumOffload = true
|
||||
}
|
||||
|
||||
err = netlink.LinkSetUp(tunLink)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -188,57 +334,29 @@ func (t *NativeTun) Close() error {
|
||||
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
|
||||
}
|
||||
|
||||
func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route {
|
||||
var routes []netlink.Route
|
||||
if len(t.options.Inet4Address) > 0 {
|
||||
if t.options.AutoRoute {
|
||||
if len(t.options.Inet4RouteAddress) > 0 {
|
||||
for _, addr := range t.options.Inet4RouteAddress {
|
||||
routes = append(routes, netlink.Route{
|
||||
Dst: &net.IPNet{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(addr.Bits(), 32),
|
||||
},
|
||||
LinkIndex: tunLink.Attrs().Index,
|
||||
Table: t.options.TableIndex,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
routes = append(routes, netlink.Route{
|
||||
Dst: &net.IPNet{
|
||||
IP: net.IPv4zero,
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
},
|
||||
LinkIndex: tunLink.Attrs().Index,
|
||||
Table: t.options.TableIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
func (t *NativeTun) TXChecksumOffload() bool {
|
||||
return t.txChecksumOffload
|
||||
}
|
||||
|
||||
func prefixToIPNet(prefix netip.Prefix) *net.IPNet {
|
||||
return &net.IPNet{
|
||||
IP: prefix.Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(prefix.Bits(), prefix.Addr().BitLen()),
|
||||
}
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
if len(t.options.Inet6RouteAddress) > 0 {
|
||||
for _, addr := range t.options.Inet6RouteAddress {
|
||||
routes = append(routes, netlink.Route{
|
||||
Dst: &net.IPNet{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(addr.Bits(), 128),
|
||||
},
|
||||
LinkIndex: tunLink.Attrs().Index,
|
||||
Table: t.options.TableIndex,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
routes = append(routes, netlink.Route{
|
||||
Dst: &net.IPNet{
|
||||
IP: net.IPv6zero,
|
||||
Mask: net.CIDRMask(0, 128),
|
||||
},
|
||||
LinkIndex: tunLink.Attrs().Index,
|
||||
Table: t.options.TableIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NativeTun) routes(tunLink netlink.Link) ([]netlink.Route, error) {
|
||||
routeRanges, err := t.options.BuildAutoRouteRanges(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return routes
|
||||
return common.Map(routeRanges, func(it netip.Prefix) netlink.Route {
|
||||
return netlink.Route{
|
||||
Dst: prefixToIPNet(it),
|
||||
LinkIndex: tunLink.Attrs().Index,
|
||||
Table: t.options.TableIndex,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -334,14 +452,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.OifName = includeInterface
|
||||
it.Goto = matchPriority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
@@ -351,14 +461,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.OifName = includeInterface
|
||||
it.Goto = matchPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
}
|
||||
}
|
||||
if p4 {
|
||||
@@ -399,14 +501,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.OifName = excludeInterface
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
@@ -416,14 +510,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.OifName = excludeInterface
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,16 +572,34 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
rules = append(rules, it)
|
||||
}
|
||||
priority++
|
||||
}
|
||||
/*if p6 {
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Dst = t.options.Inet6Address.Masked()
|
||||
it.Table = tunTableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Table = t.options.TableIndex
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}*/
|
||||
if p4 {
|
||||
priority6++
|
||||
}
|
||||
if p4 && !t.options.StrictRoute {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Invert = true
|
||||
it.Dport = netlink.NewRulePortRange(53, 53)
|
||||
it.Table = unix.RT_TABLE_MAIN
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IPProto = syscall.IPPROTO_ICMP
|
||||
@@ -504,7 +608,16 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
if p6 && !t.options.StrictRoute {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Invert = true
|
||||
it.Dport = netlink.NewRulePortRange(53, 53)
|
||||
it.Table = unix.RT_TABLE_MAIN
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IPProto = syscall.IPPROTO_ICMPV6
|
||||
@@ -513,26 +626,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
}
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Invert = true
|
||||
it.Dport = netlink.NewRulePortRange(53, 53)
|
||||
it.Table = unix.RT_TABLE_MAIN
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Invert = true
|
||||
it.Dport = netlink.NewRulePortRange(53, 53)
|
||||
it.Table = unix.RT_TABLE_MAIN
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
}
|
||||
|
||||
if p4 {
|
||||
@@ -572,36 +665,42 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
// FIXME: this match connections from public address
|
||||
if !t.options.StrictRoute {
|
||||
for _, address := range t.options.Inet6Address {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = "lo"
|
||||
it.Src = address.Masked()
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = "lo"
|
||||
it.Src = netip.PrefixFrom(netip.IPv6Unspecified(), 1)
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = "lo"
|
||||
it.Src = netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 128}), 1)
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
|
||||
priority6++
|
||||
}
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
|
||||
/*it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Invert = true
|
||||
it.IifName = "lo"
|
||||
it.Table = tunTableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IifName = "lo"
|
||||
it.Src = netip.PrefixFrom(netip.IPv6Unspecified(), 128) // not working
|
||||
it.Table = tunTableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IifName = "lo"
|
||||
it.Src = t.options.Inet6Address.Masked()
|
||||
it.Table = tunTableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)*/
|
||||
priority6++
|
||||
}
|
||||
if p4 {
|
||||
@@ -620,7 +719,11 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
}
|
||||
|
||||
func (t *NativeTun) setRoute(tunLink netlink.Link) error {
|
||||
for i, route := range t.routes(tunLink) {
|
||||
routes, err := t.routes(tunLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, route := range routes {
|
||||
err := netlink.RouteAdd(&route)
|
||||
if err != nil {
|
||||
return E.Cause(err, "add route ", i)
|
||||
@@ -651,8 +754,10 @@ func (t *NativeTun) unsetRoute() error {
|
||||
}
|
||||
|
||||
func (t *NativeTun) unsetRoute0(tunLink netlink.Link) error {
|
||||
for _, route := range t.routes(tunLink) {
|
||||
_ = netlink.RouteDel(&route)
|
||||
if routes, err := t.routes(tunLink); err == nil {
|
||||
for _, route := range routes {
|
||||
_ = netlink.RouteDel(&route)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -722,9 +827,9 @@ func (t *NativeTun) setSearchDomainForSystemdResolved() {
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next())
|
||||
}
|
||||
shell.Exec(ctlPath, "domain", t.options.Name, "~.").Start()
|
||||
go shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run()
|
||||
if t.options.AutoRoute {
|
||||
shell.Exec(ctlPath, "default-route", t.options.Name, "true").Start()
|
||||
shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Start()
|
||||
go shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run()
|
||||
go shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
|
||||
}
|
||||
}
|
||||
|
||||
84
tun_linux_flags.go
Normal file
84
tun_linux_flags.go
Normal file
@@ -0,0 +1,84 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checkVNETHDREnabled(fd int, name string) (bool, error) {
|
||||
ifr, err := unix.NewIfreq(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr)
|
||||
if err != nil {
|
||||
return false, os.NewSyscallError("TUNGETIFF", err)
|
||||
}
|
||||
return ifr.Uint16()&unix.IFF_VNET_HDR != 0, nil
|
||||
}
|
||||
|
||||
func setTCPOffload(fd int) error {
|
||||
const (
|
||||
// TODO: support TSO with ECN bits
|
||||
tunOffloads = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
||||
)
|
||||
err := unix.IoctlSetInt(fd, unix.TUNSETOFFLOAD, tunOffloads)
|
||||
if err != nil {
|
||||
return E.Cause(os.NewSyscallError("TUNSETOFFLOAD", err), "enable offload")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ifreqData struct {
|
||||
ifrName [unix.IFNAMSIZ]byte
|
||||
ifrData uintptr
|
||||
}
|
||||
|
||||
type ethtoolValue struct {
|
||||
cmd uint32
|
||||
data uint32
|
||||
}
|
||||
|
||||
//go:linkname ioctlPtr golang.org/x/sys/unix.ioctlPtr
|
||||
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error)
|
||||
|
||||
func checkChecksumOffload(name string, cmd uint32) (bool, error) {
|
||||
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
ifr := ifreqData{}
|
||||
copy(ifr.ifrName[:], name)
|
||||
data := ethtoolValue{cmd: cmd}
|
||||
ifr.ifrData = uintptr(unsafe.Pointer(&data))
|
||||
err = ioctlPtr(fd, unix.SIOCETHTOOL, unsafe.Pointer(&ifr))
|
||||
if err != nil {
|
||||
return false, os.NewSyscallError("SIOCETHTOOL ETHTOOL_GTXCSUM", err)
|
||||
}
|
||||
return data.data == 0, nil
|
||||
}
|
||||
|
||||
func setChecksumOffload(name string, cmd uint32) error {
|
||||
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
ifr := ifreqData{}
|
||||
copy(ifr.ifrName[:], name)
|
||||
data := ethtoolValue{cmd: cmd, data: 0}
|
||||
ifr.ifrData = uintptr(unsafe.Pointer(&data))
|
||||
err = ioctlPtr(fd, unix.SIOCETHTOOL, unsafe.Pointer(&ifr))
|
||||
if err != nil {
|
||||
return os.NewSyscallError("SIOCETHTOOL ETHTOOL_STXCSUM", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -10,8 +10,19 @@ import (
|
||||
var _ GVisorTun = (*NativeTun)(nil)
|
||||
|
||||
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
|
||||
if t.gsoEnabled {
|
||||
return fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU,
|
||||
GSOMaxSize: gsoMaxSize,
|
||||
RXChecksumOffload: true,
|
||||
TXChecksumOffload: t.txChecksumOffload,
|
||||
})
|
||||
}
|
||||
return fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU,
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU,
|
||||
RXChecksumOffload: true,
|
||||
TXChecksumOffload: t.txChecksumOffload,
|
||||
})
|
||||
}
|
||||
|
||||
768
tun_linux_offload.go
Normal file
768
tun_linux_offload.go
Normal file
@@ -0,0 +1,768 @@
|
||||
//go:build linux
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/clashtcpip"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
gsoMaxSize = 65536
|
||||
tcpFlagsOffset = 13
|
||||
idealBatchSize = 128
|
||||
)
|
||||
|
||||
const (
|
||||
tcpFlagFIN uint8 = 0x01
|
||||
tcpFlagPSH uint8 = 0x08
|
||||
tcpFlagACK uint8 = 0x10
|
||||
)
|
||||
|
||||
// virtioNetHdr is defined in the kernel in include/uapi/linux/virtio_net.h. The
|
||||
// kernel symbol is virtio_net_hdr.
|
||||
type virtioNetHdr struct {
|
||||
flags uint8
|
||||
gsoType uint8
|
||||
hdrLen uint16
|
||||
gsoSize uint16
|
||||
csumStart uint16
|
||||
csumOffset uint16
|
||||
}
|
||||
|
||||
func (v *virtioNetHdr) decode(b []byte) error {
|
||||
if len(b) < virtioNetHdrLen {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
copy(unsafe.Slice((*byte)(unsafe.Pointer(v)), virtioNetHdrLen), b[:virtioNetHdrLen])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *virtioNetHdr) encode(b []byte) error {
|
||||
if len(b) < virtioNetHdrLen {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
copy(b[:virtioNetHdrLen], unsafe.Slice((*byte)(unsafe.Pointer(v)), virtioNetHdrLen))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// virtioNetHdrLen is the length in bytes of virtioNetHdr. This matches the
|
||||
// shape of the C ABI for its kernel counterpart -- sizeof(virtio_net_hdr).
|
||||
virtioNetHdrLen = int(unsafe.Sizeof(virtioNetHdr{}))
|
||||
)
|
||||
|
||||
// flowKey represents the key for a flow.
|
||||
type flowKey struct {
|
||||
srcAddr, dstAddr [16]byte
|
||||
srcPort, dstPort uint16
|
||||
rxAck uint32 // varying ack values should not be coalesced. Treat them as separate flows.
|
||||
}
|
||||
|
||||
// tcpGROTable holds flow and coalescing information for the purposes of GRO.
|
||||
type tcpGROTable struct {
|
||||
itemsByFlow map[flowKey][]tcpGROItem
|
||||
itemsPool [][]tcpGROItem
|
||||
}
|
||||
|
||||
func newTCPGROTable() *tcpGROTable {
|
||||
t := &tcpGROTable{
|
||||
itemsByFlow: make(map[flowKey][]tcpGROItem, idealBatchSize),
|
||||
itemsPool: make([][]tcpGROItem, idealBatchSize),
|
||||
}
|
||||
for i := range t.itemsPool {
|
||||
t.itemsPool[i] = make([]tcpGROItem, 0, idealBatchSize)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func newFlowKey(pkt []byte, srcAddr, dstAddr, tcphOffset int) flowKey {
|
||||
key := flowKey{}
|
||||
addrSize := dstAddr - srcAddr
|
||||
copy(key.srcAddr[:], pkt[srcAddr:dstAddr])
|
||||
copy(key.dstAddr[:], pkt[dstAddr:dstAddr+addrSize])
|
||||
key.srcPort = binary.BigEndian.Uint16(pkt[tcphOffset:])
|
||||
key.dstPort = binary.BigEndian.Uint16(pkt[tcphOffset+2:])
|
||||
key.rxAck = binary.BigEndian.Uint32(pkt[tcphOffset+8:])
|
||||
return key
|
||||
}
|
||||
|
||||
// lookupOrInsert looks up a flow for the provided packet and metadata,
|
||||
// returning the packets found for the flow, or inserting a new one if none
|
||||
// is found.
|
||||
func (t *tcpGROTable) lookupOrInsert(pkt []byte, srcAddrOffset, dstAddrOffset, tcphOffset, tcphLen, bufsIndex int) ([]tcpGROItem, bool) {
|
||||
key := newFlowKey(pkt, srcAddrOffset, dstAddrOffset, tcphOffset)
|
||||
items, ok := t.itemsByFlow[key]
|
||||
if ok {
|
||||
return items, ok
|
||||
}
|
||||
// TODO: insert() performs another map lookup. This could be rearranged to avoid.
|
||||
t.insert(pkt, srcAddrOffset, dstAddrOffset, tcphOffset, tcphLen, bufsIndex)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// insert an item in the table for the provided packet and packet metadata.
|
||||
func (t *tcpGROTable) insert(pkt []byte, srcAddrOffset, dstAddrOffset, tcphOffset, tcphLen, bufsIndex int) {
|
||||
key := newFlowKey(pkt, srcAddrOffset, dstAddrOffset, tcphOffset)
|
||||
item := tcpGROItem{
|
||||
key: key,
|
||||
bufsIndex: uint16(bufsIndex),
|
||||
gsoSize: uint16(len(pkt[tcphOffset+tcphLen:])),
|
||||
iphLen: uint8(tcphOffset),
|
||||
tcphLen: uint8(tcphLen),
|
||||
sentSeq: binary.BigEndian.Uint32(pkt[tcphOffset+4:]),
|
||||
pshSet: pkt[tcphOffset+tcpFlagsOffset]&tcpFlagPSH != 0,
|
||||
}
|
||||
items, ok := t.itemsByFlow[key]
|
||||
if !ok {
|
||||
items = t.newItems()
|
||||
}
|
||||
items = append(items, item)
|
||||
t.itemsByFlow[key] = items
|
||||
}
|
||||
|
||||
func (t *tcpGROTable) updateAt(item tcpGROItem, i int) {
|
||||
items, _ := t.itemsByFlow[item.key]
|
||||
items[i] = item
|
||||
}
|
||||
|
||||
func (t *tcpGROTable) deleteAt(key flowKey, i int) {
|
||||
items, _ := t.itemsByFlow[key]
|
||||
items = append(items[:i], items[i+1:]...)
|
||||
t.itemsByFlow[key] = items
|
||||
}
|
||||
|
||||
// tcpGROItem represents bookkeeping data for a TCP packet during the lifetime
|
||||
// of a GRO evaluation across a vector of packets.
|
||||
type tcpGROItem struct {
|
||||
key flowKey
|
||||
sentSeq uint32 // the sequence number
|
||||
bufsIndex uint16 // the index into the original bufs slice
|
||||
numMerged uint16 // the number of packets merged into this item
|
||||
gsoSize uint16 // payload size
|
||||
iphLen uint8 // ip header len
|
||||
tcphLen uint8 // tcp header len
|
||||
pshSet bool // psh flag is set
|
||||
}
|
||||
|
||||
func (t *tcpGROTable) newItems() []tcpGROItem {
|
||||
var items []tcpGROItem
|
||||
items, t.itemsPool = t.itemsPool[len(t.itemsPool)-1], t.itemsPool[:len(t.itemsPool)-1]
|
||||
return items
|
||||
}
|
||||
|
||||
func (t *tcpGROTable) reset() {
|
||||
for k, items := range t.itemsByFlow {
|
||||
items = items[:0]
|
||||
t.itemsPool = append(t.itemsPool, items)
|
||||
delete(t.itemsByFlow, k)
|
||||
}
|
||||
}
|
||||
|
||||
// canCoalesce represents the outcome of checking if two TCP packets are
|
||||
// candidates for coalescing.
|
||||
type canCoalesce int
|
||||
|
||||
const (
|
||||
coalescePrepend canCoalesce = -1
|
||||
coalesceUnavailable canCoalesce = 0
|
||||
coalesceAppend canCoalesce = 1
|
||||
)
|
||||
|
||||
// tcpPacketsCanCoalesce evaluates if pkt can be coalesced with the packet
|
||||
// described by item. This function makes considerations that match the kernel's
|
||||
// GRO self tests, which can be found in tools/testing/selftests/net/gro.c.
|
||||
func tcpPacketsCanCoalesce(pkt []byte, iphLen, tcphLen uint8, seq uint32, pshSet bool, gsoSize uint16, item tcpGROItem, bufs [][]byte, bufsOffset int) canCoalesce {
|
||||
pktTarget := bufs[item.bufsIndex][bufsOffset:]
|
||||
if tcphLen != item.tcphLen {
|
||||
// cannot coalesce with unequal tcp options len
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if tcphLen > 20 {
|
||||
if !bytes.Equal(pkt[iphLen+20:iphLen+tcphLen], pktTarget[item.iphLen+20:iphLen+tcphLen]) {
|
||||
// cannot coalesce with unequal tcp options
|
||||
return coalesceUnavailable
|
||||
}
|
||||
}
|
||||
if pkt[0]>>4 == 6 {
|
||||
if pkt[0] != pktTarget[0] || pkt[1]>>4 != pktTarget[1]>>4 {
|
||||
// cannot coalesce with unequal Traffic class values
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if pkt[7] != pktTarget[7] {
|
||||
// cannot coalesce with unequal Hop limit values
|
||||
return coalesceUnavailable
|
||||
}
|
||||
} else {
|
||||
if pkt[1] != pktTarget[1] {
|
||||
// cannot coalesce with unequal ToS values
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if pkt[6]>>5 != pktTarget[6]>>5 {
|
||||
// cannot coalesce with unequal DF or reserved bits. MF is checked
|
||||
// further up the stack.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if pkt[8] != pktTarget[8] {
|
||||
// cannot coalesce with unequal TTL values
|
||||
return coalesceUnavailable
|
||||
}
|
||||
}
|
||||
// seq adjacency
|
||||
lhsLen := item.gsoSize
|
||||
lhsLen += item.numMerged * item.gsoSize
|
||||
if seq == item.sentSeq+uint32(lhsLen) { // pkt aligns following item from a seq num perspective
|
||||
if item.pshSet {
|
||||
// We cannot append to a segment that has the PSH flag set, PSH
|
||||
// can only be set on the final segment in a reassembled group.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if len(pktTarget[iphLen+tcphLen:])%int(item.gsoSize) != 0 {
|
||||
// A smaller than gsoSize packet has been appended previously.
|
||||
// Nothing can come after a smaller packet on the end.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if gsoSize > item.gsoSize {
|
||||
// We cannot have a larger packet following a smaller one.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
return coalesceAppend
|
||||
} else if seq+uint32(gsoSize) == item.sentSeq { // pkt aligns in front of item from a seq num perspective
|
||||
if pshSet {
|
||||
// We cannot prepend with a segment that has the PSH flag set, PSH
|
||||
// can only be set on the final segment in a reassembled group.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if gsoSize < item.gsoSize {
|
||||
// We cannot have a larger packet following a smaller one.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
if gsoSize > item.gsoSize && item.numMerged > 0 {
|
||||
// There's at least one previous merge, and we're larger than all
|
||||
// previous. This would put multiple smaller packets on the end.
|
||||
return coalesceUnavailable
|
||||
}
|
||||
return coalescePrepend
|
||||
}
|
||||
return coalesceUnavailable
|
||||
}
|
||||
|
||||
func tcpChecksumValid(pkt []byte, iphLen uint8, isV6 bool) bool {
|
||||
srcAddrAt := ipv4SrcAddrOffset
|
||||
addrSize := 4
|
||||
if isV6 {
|
||||
srcAddrAt = ipv6SrcAddrOffset
|
||||
addrSize = 16
|
||||
}
|
||||
tcpTotalLen := uint16(len(pkt) - int(iphLen))
|
||||
tcpCSumNoFold := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, pkt[srcAddrAt:srcAddrAt+addrSize], pkt[srcAddrAt+addrSize:srcAddrAt+addrSize*2], tcpTotalLen)
|
||||
return ^checksumFold(pkt[iphLen:], tcpCSumNoFold) == 0
|
||||
}
|
||||
|
||||
// coalesceResult represents the result of attempting to coalesce two TCP
|
||||
// packets.
|
||||
type coalesceResult int
|
||||
|
||||
const (
|
||||
coalesceInsufficientCap coalesceResult = iota
|
||||
coalescePSHEnding
|
||||
coalesceItemInvalidCSum
|
||||
coalescePktInvalidCSum
|
||||
coalesceSuccess
|
||||
)
|
||||
|
||||
// coalesceTCPPackets attempts to coalesce pkt with the packet described by
|
||||
// item, returning the outcome. This function may swap bufs elements in the
|
||||
// event of a prepend as item's bufs index is already being tracked for writing
|
||||
// to a Device.
|
||||
func coalesceTCPPackets(mode canCoalesce, pkt []byte, pktBuffsIndex int, gsoSize uint16, seq uint32, pshSet bool, item *tcpGROItem, bufs [][]byte, bufsOffset int, isV6 bool) coalesceResult {
|
||||
var pktHead []byte // the packet that will end up at the front
|
||||
headersLen := item.iphLen + item.tcphLen
|
||||
coalescedLen := len(bufs[item.bufsIndex][bufsOffset:]) + len(pkt) - int(headersLen)
|
||||
|
||||
// Copy data
|
||||
if mode == coalescePrepend {
|
||||
pktHead = pkt
|
||||
if cap(pkt)-bufsOffset < coalescedLen {
|
||||
// We don't want to allocate a new underlying array if capacity is
|
||||
// too small.
|
||||
return coalesceInsufficientCap
|
||||
}
|
||||
if pshSet {
|
||||
return coalescePSHEnding
|
||||
}
|
||||
if item.numMerged == 0 {
|
||||
if !tcpChecksumValid(bufs[item.bufsIndex][bufsOffset:], item.iphLen, isV6) {
|
||||
return coalesceItemInvalidCSum
|
||||
}
|
||||
}
|
||||
if !tcpChecksumValid(pkt, item.iphLen, isV6) {
|
||||
return coalescePktInvalidCSum
|
||||
}
|
||||
item.sentSeq = seq
|
||||
extendBy := coalescedLen - len(pktHead)
|
||||
bufs[pktBuffsIndex] = append(bufs[pktBuffsIndex], make([]byte, extendBy)...)
|
||||
copy(bufs[pktBuffsIndex][bufsOffset+len(pkt):], bufs[item.bufsIndex][bufsOffset+int(headersLen):])
|
||||
// Flip the slice headers in bufs as part of prepend. The index of item
|
||||
// is already being tracked for writing.
|
||||
bufs[item.bufsIndex], bufs[pktBuffsIndex] = bufs[pktBuffsIndex], bufs[item.bufsIndex]
|
||||
} else {
|
||||
pktHead = bufs[item.bufsIndex][bufsOffset:]
|
||||
if cap(pktHead)-bufsOffset < coalescedLen {
|
||||
// We don't want to allocate a new underlying array if capacity is
|
||||
// too small.
|
||||
return coalesceInsufficientCap
|
||||
}
|
||||
if item.numMerged == 0 {
|
||||
if !tcpChecksumValid(bufs[item.bufsIndex][bufsOffset:], item.iphLen, isV6) {
|
||||
return coalesceItemInvalidCSum
|
||||
}
|
||||
}
|
||||
if !tcpChecksumValid(pkt, item.iphLen, isV6) {
|
||||
return coalescePktInvalidCSum
|
||||
}
|
||||
if pshSet {
|
||||
// We are appending a segment with PSH set.
|
||||
item.pshSet = pshSet
|
||||
pktHead[item.iphLen+tcpFlagsOffset] |= tcpFlagPSH
|
||||
}
|
||||
extendBy := len(pkt) - int(headersLen)
|
||||
bufs[item.bufsIndex] = append(bufs[item.bufsIndex], make([]byte, extendBy)...)
|
||||
copy(bufs[item.bufsIndex][bufsOffset+len(pktHead):], pkt[headersLen:])
|
||||
}
|
||||
|
||||
if gsoSize > item.gsoSize {
|
||||
item.gsoSize = gsoSize
|
||||
}
|
||||
|
||||
item.numMerged++
|
||||
return coalesceSuccess
|
||||
}
|
||||
|
||||
const (
|
||||
ipv4FlagMoreFragments uint8 = 0x20
|
||||
)
|
||||
|
||||
const (
|
||||
ipv4SrcAddrOffset = 12
|
||||
ipv6SrcAddrOffset = 8
|
||||
maxUint16 = 1<<16 - 1
|
||||
)
|
||||
|
||||
type tcpGROResult int
|
||||
|
||||
const (
|
||||
tcpGROResultNoop tcpGROResult = iota
|
||||
tcpGROResultTableInsert
|
||||
tcpGROResultCoalesced
|
||||
)
|
||||
|
||||
// tcpGRO evaluates the TCP packet at pktI in bufs for coalescing with
|
||||
// existing packets tracked in table. It returns a tcpGROResultNoop when no
|
||||
// action was taken, tcpGROResultTableInsert when the evaluated packet was
|
||||
// inserted into table, and tcpGROResultCoalesced when the evaluated packet was
|
||||
// coalesced with another packet in table.
|
||||
func tcpGRO(bufs [][]byte, offset int, pktI int, table *tcpGROTable, isV6 bool) tcpGROResult {
|
||||
pkt := bufs[pktI][offset:]
|
||||
if len(pkt) > maxUint16 {
|
||||
// A valid IPv4 or IPv6 packet will never exceed this.
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
iphLen := int((pkt[0] & 0x0F) * 4)
|
||||
if isV6 {
|
||||
iphLen = 40
|
||||
ipv6HPayloadLen := int(binary.BigEndian.Uint16(pkt[4:]))
|
||||
if ipv6HPayloadLen != len(pkt)-iphLen {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
} else {
|
||||
totalLen := int(binary.BigEndian.Uint16(pkt[2:]))
|
||||
if totalLen != len(pkt) {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
}
|
||||
if len(pkt) < iphLen {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
tcphLen := int((pkt[iphLen+12] >> 4) * 4)
|
||||
if tcphLen < 20 || tcphLen > 60 {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
if len(pkt) < iphLen+tcphLen {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
if !isV6 {
|
||||
if pkt[6]&ipv4FlagMoreFragments != 0 || pkt[6]<<3 != 0 || pkt[7] != 0 {
|
||||
// no GRO support for fragmented segments for now
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
}
|
||||
tcpFlags := pkt[iphLen+tcpFlagsOffset]
|
||||
var pshSet bool
|
||||
// not a candidate if any non-ACK flags (except PSH+ACK) are set
|
||||
if tcpFlags != tcpFlagACK {
|
||||
if pkt[iphLen+tcpFlagsOffset] != tcpFlagACK|tcpFlagPSH {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
pshSet = true
|
||||
}
|
||||
gsoSize := uint16(len(pkt) - tcphLen - iphLen)
|
||||
// not a candidate if payload len is 0
|
||||
if gsoSize < 1 {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
seq := binary.BigEndian.Uint32(pkt[iphLen+4:])
|
||||
srcAddrOffset := ipv4SrcAddrOffset
|
||||
addrLen := 4
|
||||
if isV6 {
|
||||
srcAddrOffset = ipv6SrcAddrOffset
|
||||
addrLen = 16
|
||||
}
|
||||
items, existing := table.lookupOrInsert(pkt, srcAddrOffset, srcAddrOffset+addrLen, iphLen, tcphLen, pktI)
|
||||
if !existing {
|
||||
return tcpGROResultNoop
|
||||
}
|
||||
for i := len(items) - 1; i >= 0; i-- {
|
||||
// In the best case of packets arriving in order iterating in reverse is
|
||||
// more efficient if there are multiple items for a given flow. This
|
||||
// also enables a natural table.deleteAt() in the
|
||||
// coalesceItemInvalidCSum case without the need for index tracking.
|
||||
// This algorithm makes a best effort to coalesce in the event of
|
||||
// unordered packets, where pkt may land anywhere in items from a
|
||||
// sequence number perspective, however once an item is inserted into
|
||||
// the table it is never compared across other items later.
|
||||
item := items[i]
|
||||
can := tcpPacketsCanCoalesce(pkt, uint8(iphLen), uint8(tcphLen), seq, pshSet, gsoSize, item, bufs, offset)
|
||||
if can != coalesceUnavailable {
|
||||
result := coalesceTCPPackets(can, pkt, pktI, gsoSize, seq, pshSet, &item, bufs, offset, isV6)
|
||||
switch result {
|
||||
case coalesceSuccess:
|
||||
table.updateAt(item, i)
|
||||
return tcpGROResultCoalesced
|
||||
case coalesceItemInvalidCSum:
|
||||
// delete the item with an invalid csum
|
||||
table.deleteAt(item.key, i)
|
||||
case coalescePktInvalidCSum:
|
||||
// no point in inserting an item that we can't coalesce
|
||||
return tcpGROResultNoop
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
// failed to coalesce with any other packets; store the item in the flow
|
||||
table.insert(pkt, srcAddrOffset, srcAddrOffset+addrLen, iphLen, tcphLen, pktI)
|
||||
return tcpGROResultTableInsert
|
||||
}
|
||||
|
||||
func isTCP4NoIPOptions(b []byte) bool {
|
||||
if len(b) < 40 {
|
||||
return false
|
||||
}
|
||||
if b[0]>>4 != 4 {
|
||||
return false
|
||||
}
|
||||
if b[0]&0x0F != 5 {
|
||||
return false
|
||||
}
|
||||
if b[9] != unix.IPPROTO_TCP {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isTCP6NoEH(b []byte) bool {
|
||||
if len(b) < 60 {
|
||||
return false
|
||||
}
|
||||
if b[0]>>4 != 6 {
|
||||
return false
|
||||
}
|
||||
if b[6] != unix.IPPROTO_TCP {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// applyCoalesceAccounting updates bufs to account for coalescing based on the
|
||||
// metadata found in table.
|
||||
func applyCoalesceAccounting(bufs [][]byte, offset int, table *tcpGROTable, isV6 bool) error {
|
||||
for _, items := range table.itemsByFlow {
|
||||
for _, item := range items {
|
||||
if item.numMerged > 0 {
|
||||
hdr := virtioNetHdr{
|
||||
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, // this turns into CHECKSUM_PARTIAL in the skb
|
||||
hdrLen: uint16(item.iphLen + item.tcphLen),
|
||||
gsoSize: item.gsoSize,
|
||||
csumStart: uint16(item.iphLen),
|
||||
csumOffset: 16,
|
||||
}
|
||||
pkt := bufs[item.bufsIndex][offset:]
|
||||
|
||||
// Recalculate the total len (IPv4) or payload len (IPv6).
|
||||
// Recalculate the (IPv4) header checksum.
|
||||
if isV6 {
|
||||
hdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV6
|
||||
binary.BigEndian.PutUint16(pkt[4:], uint16(len(pkt))-uint16(item.iphLen)) // set new IPv6 header payload len
|
||||
} else {
|
||||
hdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV4
|
||||
pkt[10], pkt[11] = 0, 0
|
||||
binary.BigEndian.PutUint16(pkt[2:], uint16(len(pkt))) // set new total length
|
||||
iphCSum := ^checksumFold(pkt[:item.iphLen], 0) // compute IPv4 header checksum
|
||||
binary.BigEndian.PutUint16(pkt[10:], iphCSum) // set IPv4 header checksum field
|
||||
}
|
||||
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate the pseudo header checksum and place it at the TCP
|
||||
// checksum offset. Downstream checksum offloading will combine
|
||||
// this with computation of the tcp header and payload checksum.
|
||||
addrLen := 4
|
||||
addrOffset := ipv4SrcAddrOffset
|
||||
if isV6 {
|
||||
addrLen = 16
|
||||
addrOffset = ipv6SrcAddrOffset
|
||||
}
|
||||
srcAddrAt := offset + addrOffset
|
||||
srcAddr := bufs[item.bufsIndex][srcAddrAt : srcAddrAt+addrLen]
|
||||
dstAddr := bufs[item.bufsIndex][srcAddrAt+addrLen : srcAddrAt+addrLen*2]
|
||||
psum := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
|
||||
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], checksumFold([]byte{}, psum))
|
||||
} else {
|
||||
hdr := virtioNetHdr{}
|
||||
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleGRO evaluates bufs for GRO, and writes the indices of the resulting
|
||||
// packets into toWrite. toWrite, tcp4Table, and tcp6Table should initially be
|
||||
// empty (but non-nil), and are passed in to save allocs as the caller may reset
|
||||
// and recycle them across vectors of packets.
|
||||
func handleGRO(bufs [][]byte, offset int, tcp4Table, tcp6Table *tcpGROTable, toWrite *[]int) error {
|
||||
for i := range bufs {
|
||||
if offset < virtioNetHdrLen || offset > len(bufs[i])-1 {
|
||||
return errors.New("invalid offset")
|
||||
}
|
||||
var result tcpGROResult
|
||||
switch {
|
||||
case isTCP4NoIPOptions(bufs[i][offset:]): // ipv4 packets w/IP options do not coalesce
|
||||
result = tcpGRO(bufs, offset, i, tcp4Table, false)
|
||||
case isTCP6NoEH(bufs[i][offset:]): // ipv6 packets w/extension headers do not coalesce
|
||||
result = tcpGRO(bufs, offset, i, tcp6Table, true)
|
||||
}
|
||||
switch result {
|
||||
case tcpGROResultNoop:
|
||||
hdr := virtioNetHdr{}
|
||||
err := hdr.encode(bufs[i][offset-virtioNetHdrLen:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fallthrough
|
||||
case tcpGROResultTableInsert:
|
||||
*toWrite = append(*toWrite, i)
|
||||
}
|
||||
}
|
||||
err4 := applyCoalesceAccounting(bufs, offset, tcp4Table, false)
|
||||
err6 := applyCoalesceAccounting(bufs, offset, tcp6Table, true)
|
||||
return E.Errors(err4, err6)
|
||||
}
|
||||
|
||||
// tcpTSO splits packets from in into outBuffs, writing the size of each
|
||||
// element into sizes. It returns the number of buffers populated, and/or an
|
||||
// error.
|
||||
func tcpTSO(in []byte, hdr virtioNetHdr, outBuffs [][]byte, sizes []int, outOffset int) (int, error) {
|
||||
iphLen := int(hdr.csumStart)
|
||||
srcAddrOffset := ipv6SrcAddrOffset
|
||||
addrLen := 16
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV4 {
|
||||
in[10], in[11] = 0, 0 // clear ipv4 header checksum
|
||||
srcAddrOffset = ipv4SrcAddrOffset
|
||||
addrLen = 4
|
||||
}
|
||||
tcpCSumAt := int(hdr.csumStart + hdr.csumOffset)
|
||||
in[tcpCSumAt], in[tcpCSumAt+1] = 0, 0 // clear tcp checksum
|
||||
firstTCPSeqNum := binary.BigEndian.Uint32(in[hdr.csumStart+4:])
|
||||
nextSegmentDataAt := int(hdr.hdrLen)
|
||||
i := 0
|
||||
for ; nextSegmentDataAt < len(in); i++ {
|
||||
if i == len(outBuffs) {
|
||||
return i - 1, ErrTooManySegments
|
||||
}
|
||||
nextSegmentEnd := nextSegmentDataAt + int(hdr.gsoSize)
|
||||
if nextSegmentEnd > len(in) {
|
||||
nextSegmentEnd = len(in)
|
||||
}
|
||||
segmentDataLen := nextSegmentEnd - nextSegmentDataAt
|
||||
totalLen := int(hdr.hdrLen) + segmentDataLen
|
||||
sizes[i] = totalLen
|
||||
out := outBuffs[i][outOffset:]
|
||||
|
||||
copy(out, in[:iphLen])
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV4 {
|
||||
// For IPv4 we are responsible for incrementing the ID field,
|
||||
// updating the total len field, and recalculating the header
|
||||
// checksum.
|
||||
if i > 0 {
|
||||
id := binary.BigEndian.Uint16(out[4:])
|
||||
id += uint16(i)
|
||||
binary.BigEndian.PutUint16(out[4:], id)
|
||||
}
|
||||
binary.BigEndian.PutUint16(out[2:], uint16(totalLen))
|
||||
ipv4CSum := ^checksumFold(out[:iphLen], 0)
|
||||
binary.BigEndian.PutUint16(out[10:], ipv4CSum)
|
||||
} else {
|
||||
// For IPv6 we are responsible for updating the payload length field.
|
||||
binary.BigEndian.PutUint16(out[4:], uint16(totalLen-iphLen))
|
||||
}
|
||||
|
||||
// TCP header
|
||||
copy(out[hdr.csumStart:hdr.hdrLen], in[hdr.csumStart:hdr.hdrLen])
|
||||
tcpSeq := firstTCPSeqNum + uint32(hdr.gsoSize*uint16(i))
|
||||
binary.BigEndian.PutUint32(out[hdr.csumStart+4:], tcpSeq)
|
||||
if nextSegmentEnd != len(in) {
|
||||
// FIN and PSH should only be set on last segment
|
||||
clearFlags := tcpFlagFIN | tcpFlagPSH
|
||||
out[hdr.csumStart+tcpFlagsOffset] &^= clearFlags
|
||||
}
|
||||
|
||||
// payload
|
||||
copy(out[hdr.hdrLen:], in[nextSegmentDataAt:nextSegmentEnd])
|
||||
|
||||
// TCP checksum
|
||||
tcpHLen := int(hdr.hdrLen - hdr.csumStart)
|
||||
tcpLenForPseudo := uint16(tcpHLen + segmentDataLen)
|
||||
tcpCSumNoFold := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, in[srcAddrOffset:srcAddrOffset+addrLen], in[srcAddrOffset+addrLen:srcAddrOffset+addrLen*2], tcpLenForPseudo)
|
||||
tcpCSum := ^checksumFold(out[hdr.csumStart:totalLen], tcpCSumNoFold)
|
||||
binary.BigEndian.PutUint16(out[hdr.csumStart+hdr.csumOffset:], tcpCSum)
|
||||
|
||||
nextSegmentDataAt += int(hdr.gsoSize)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func gsoNoneChecksum(in []byte, cSumStart, cSumOffset uint16) error {
|
||||
cSumAt := cSumStart + cSumOffset
|
||||
// The initial value at the checksum offset should be summed with the
|
||||
// checksum we compute. This is typically the pseudo-header checksum.
|
||||
initial := binary.BigEndian.Uint16(in[cSumAt:])
|
||||
in[cSumAt], in[cSumAt+1] = 0, 0
|
||||
binary.BigEndian.PutUint16(in[cSumAt:], ^checksumFold(in[cSumStart:], uint64(initial)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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:]
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_NONE {
|
||||
if hdr.flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
|
||||
// This means CHECKSUM_PARTIAL in skb context. We are responsible
|
||||
// for computing the checksum starting at hdr.csumStart and placing
|
||||
// at hdr.csumOffset.
|
||||
err = gsoNoneChecksum(in, hdr.csumStart, hdr.csumOffset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if len(in) > len(bufs[0][offset:]) {
|
||||
return 0, fmt.Errorf("read len %d overflows bufs element len %d", len(in), len(bufs[0][offset:]))
|
||||
}
|
||||
n := copy(bufs[0][offset:], in)
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 {
|
||||
return 0, fmt.Errorf("unsupported virtio GSO type: %d", hdr.gsoType)
|
||||
}
|
||||
|
||||
ipVersion := in[0] >> 4
|
||||
switch ipVersion {
|
||||
case 4:
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %d", ipVersion, hdr.gsoType)
|
||||
}
|
||||
case 6:
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %d", ipVersion, hdr.gsoType)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid ip header version: %d", ipVersion)
|
||||
}
|
||||
|
||||
if len(in) <= int(hdr.csumStart+12) {
|
||||
return 0, errors.New("packet is too short")
|
||||
}
|
||||
// Don't trust hdr.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 TCP header length and add it onto
|
||||
// csumStart, which is synonymous for IP header length.
|
||||
tcpHLen := uint16(in[hdr.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)
|
||||
}
|
||||
hdr.hdrLen = hdr.csumStart + tcpHLen
|
||||
|
||||
if len(in) < int(hdr.hdrLen) {
|
||||
return 0, fmt.Errorf("length of packet (%d) < virtioNetHdr.hdrLen (%d)", len(in), hdr.hdrLen)
|
||||
}
|
||||
|
||||
if hdr.hdrLen < hdr.csumStart {
|
||||
return 0, fmt.Errorf("virtioNetHdr.hdrLen (%d) < virtioNetHdr.csumStart (%d)", hdr.hdrLen, hdr.csumStart)
|
||||
}
|
||||
cSumAt := int(hdr.csumStart + hdr.csumOffset)
|
||||
if cSumAt+1 >= len(in) {
|
||||
return 0, fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(in))
|
||||
}
|
||||
|
||||
return tcpTSO(in, hdr, bufs, sizes, offset)
|
||||
}
|
||||
|
||||
func checksumNoFold(b []byte, initial uint64) uint64 {
|
||||
return initial + uint64(clashtcpip.Sum(b))
|
||||
}
|
||||
|
||||
func checksumFold(b []byte, initial uint64) uint16 {
|
||||
ac := checksumNoFold(b, initial)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
return uint16(ac)
|
||||
}
|
||||
|
||||
func pseudoHeaderChecksumNoFold(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint64 {
|
||||
sum := checksumNoFold(srcAddr, 0)
|
||||
sum = checksumNoFold(dstAddr, sum)
|
||||
sum = checksumNoFold([]byte{0, protocol}, sum)
|
||||
tmp := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(tmp, totalLen)
|
||||
return checksumNoFold(tmp, sum)
|
||||
}
|
||||
5
tun_linux_offload_errors.go
Normal file
5
tun_linux_offload_errors.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package tun
|
||||
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
var ErrTooManySegments = E.New("too many segments")
|
||||
5
tun_nonlinux.go
Normal file
5
tun_nonlinux.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !linux
|
||||
|
||||
package tun
|
||||
|
||||
const OffloadOffset = 0
|
||||
95
tun_rules.go
95
tun_rules.go
@@ -2,13 +2,17 @@ package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -96,3 +100,94 @@ func buildExcludedRanges(includeRanges []ranges.Range[uint32], excludeRanges []r
|
||||
}
|
||||
return ranges.Merge(uidRanges)
|
||||
}
|
||||
|
||||
const autoRouteUseSubRanges = runtime.GOOS == "darwin"
|
||||
|
||||
func (o *Options) BuildAutoRouteRanges(underNetworkExtension bool) ([]netip.Prefix, error) {
|
||||
var routeRanges []netip.Prefix
|
||||
if o.AutoRoute && len(o.Inet4Address) > 0 {
|
||||
var inet4Ranges []netip.Prefix
|
||||
if len(o.Inet4RouteAddress) > 0 {
|
||||
inet4Ranges = o.Inet4RouteAddress
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, address := range o.Inet4Address {
|
||||
if address.Bits() < 32 {
|
||||
inet4Ranges = append(inet4Ranges, address.Masked())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if autoRouteUseSubRanges && !underNetworkExtension {
|
||||
inet4Ranges = []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 1}), 8),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 2}), 7),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 4}), 6),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 8}), 5),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 16}), 4),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 32}), 3),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 64}), 2),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{0: 128}), 1),
|
||||
}
|
||||
} else {
|
||||
inet4Ranges = []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0)}
|
||||
}
|
||||
if len(o.Inet4RouteExcludeAddress) == 0 {
|
||||
routeRanges = append(routeRanges, inet4Ranges...)
|
||||
} else {
|
||||
var builder netipx.IPSetBuilder
|
||||
for _, inet4Range := range inet4Ranges {
|
||||
builder.AddPrefix(inet4Range)
|
||||
}
|
||||
for _, prefix := range o.Inet4RouteExcludeAddress {
|
||||
builder.RemovePrefix(prefix)
|
||||
}
|
||||
resultSet, err := builder.IPSet()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "build IPv4 route address")
|
||||
}
|
||||
routeRanges = append(routeRanges, resultSet.Prefixes()...)
|
||||
}
|
||||
}
|
||||
if len(o.Inet6Address) > 0 {
|
||||
var inet6Ranges []netip.Prefix
|
||||
if len(o.Inet6RouteAddress) > 0 {
|
||||
inet6Ranges = o.Inet6RouteAddress
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, address := range o.Inet6Address {
|
||||
if address.Bits() < 32 {
|
||||
inet6Ranges = append(inet6Ranges, address.Masked())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if autoRouteUseSubRanges && !underNetworkExtension {
|
||||
inet6Ranges = []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 1}), 8),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 2}), 7),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 4}), 6),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 8}), 5),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 16}), 4),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 32}), 3),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 64}), 2),
|
||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 128}), 1),
|
||||
}
|
||||
} else {
|
||||
inet6Ranges = []netip.Prefix{netip.PrefixFrom(netip.IPv6Unspecified(), 0)}
|
||||
}
|
||||
if len(o.Inet6RouteExcludeAddress) == 0 {
|
||||
routeRanges = append(routeRanges, inet6Ranges...)
|
||||
} else {
|
||||
var builder netipx.IPSetBuilder
|
||||
for _, inet6Range := range inet6Ranges {
|
||||
builder.AddPrefix(inet6Range)
|
||||
}
|
||||
for _, prefix := range o.Inet6RouteExcludeAddress {
|
||||
builder.RemovePrefix(prefix)
|
||||
}
|
||||
resultSet, err := builder.IPSet()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "build IPv6 route address")
|
||||
}
|
||||
routeRanges = append(routeRanges, resultSet.Prefixes()...)
|
||||
}
|
||||
}
|
||||
return routeRanges, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@@ -17,9 +16,9 @@ import (
|
||||
"github.com/sagernet/sing-tun/internal/winsys"
|
||||
"github.com/sagernet/sing-tun/internal/wintun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/windnsapi"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -35,7 +34,7 @@ type NativeTun struct {
|
||||
rate rateJuggler
|
||||
running sync.WaitGroup
|
||||
closeOnce sync.Once
|
||||
close int32
|
||||
close atomic.Int32
|
||||
fwpmSession uintptr
|
||||
}
|
||||
|
||||
@@ -88,38 +87,22 @@ func (t *NativeTun) configure() error {
|
||||
return E.Cause(err, "set ipv6 dns")
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet4Address) > 0 || len(t.options.Inet6Address) > 0 {
|
||||
_ = luid.DisableDNSRegistration()
|
||||
}
|
||||
if t.options.AutoRoute {
|
||||
if len(t.options.Inet4Address) > 0 {
|
||||
if len(t.options.Inet4RouteAddress) > 0 {
|
||||
for _, addr := range t.options.Inet4RouteAddress {
|
||||
err := luid.AddRoute(addr, netip.IPv4Unspecified(), 0)
|
||||
if err != nil {
|
||||
return E.Cause(err, "add ipv4 route: ", addr)
|
||||
}
|
||||
}
|
||||
routeRanges, err := t.options.BuildAutoRouteRanges(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, routeRange := range routeRanges {
|
||||
if routeRange.Addr().Is4() {
|
||||
err = luid.AddRoute(routeRange, netip.IPv4Unspecified(), 0)
|
||||
} else {
|
||||
err := luid.AddRoute(netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.IPv4Unspecified(), 0)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 route")
|
||||
}
|
||||
err = luid.AddRoute(routeRange, netip.IPv6Unspecified(), 0)
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
if len(t.options.Inet6RouteAddress) > 0 {
|
||||
for _, addr := range t.options.Inet6RouteAddress {
|
||||
err := luid.AddRoute(addr, netip.IPv6Unspecified(), 0)
|
||||
if err != nil {
|
||||
return E.Cause(err, "add ipv6 route: ", addr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err := luid.AddRoute(netip.PrefixFrom(netip.IPv6Unspecified(), 0), netip.IPv6Unspecified(), 0)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 route")
|
||||
}
|
||||
}
|
||||
}
|
||||
err := windnsapi.FlushResolverCache()
|
||||
err = windnsapi.FlushResolverCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -351,13 +334,13 @@ func (t *NativeTun) ReadPacket() ([]byte, func(), error) {
|
||||
t.running.Add(1)
|
||||
defer t.running.Done()
|
||||
retry:
|
||||
if atomic.LoadInt32(&t.close) == 1 {
|
||||
if t.close.Load() == 1 {
|
||||
return nil, nil, os.ErrClosed
|
||||
}
|
||||
start := nanotime()
|
||||
shouldSpin := atomic.LoadUint64(&t.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&t.rate.nextStartTime)) <= rateMeasurementGranularity*2
|
||||
shouldSpin := t.rate.current.Load() >= spinloopRateThreshold && uint64(start-t.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2
|
||||
for {
|
||||
if atomic.LoadInt32(&t.close) == 1 {
|
||||
if t.close.Load() == 1 {
|
||||
return nil, nil, os.ErrClosed
|
||||
}
|
||||
packet, err := t.session.ReceivePacket()
|
||||
@@ -386,13 +369,13 @@ func (t *NativeTun) ReadFunc(block func(b []byte)) error {
|
||||
t.running.Add(1)
|
||||
defer t.running.Done()
|
||||
retry:
|
||||
if atomic.LoadInt32(&t.close) == 1 {
|
||||
if t.close.Load() == 1 {
|
||||
return os.ErrClosed
|
||||
}
|
||||
start := nanotime()
|
||||
shouldSpin := atomic.LoadUint64(&t.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&t.rate.nextStartTime)) <= rateMeasurementGranularity*2
|
||||
shouldSpin := t.rate.current.Load() >= spinloopRateThreshold && uint64(start-t.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2
|
||||
for {
|
||||
if atomic.LoadInt32(&t.close) == 1 {
|
||||
if t.close.Load() == 1 {
|
||||
return os.ErrClosed
|
||||
}
|
||||
packet, err := t.session.ReceivePacket()
|
||||
@@ -422,7 +405,7 @@ retry:
|
||||
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
t.running.Add(1)
|
||||
defer t.running.Done()
|
||||
if atomic.LoadInt32(&t.close) == 1 {
|
||||
if t.close.Load() == 1 {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
t.rate.update(uint64(len(p)))
|
||||
@@ -444,7 +427,7 @@ func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
func (t *NativeTun) write(packetElementList [][]byte) (n int, err error) {
|
||||
t.running.Add(1)
|
||||
defer t.running.Done()
|
||||
if atomic.LoadInt32(&t.close) == 1 {
|
||||
if t.close.Load() == 1 {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
var packetSize int
|
||||
@@ -470,10 +453,6 @@ func (t *NativeTun) write(packetElementList [][]byte) (n int, err error) {
|
||||
return 0, fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
|
||||
func (t *NativeTun) CreateVectorisedWriter() N.VectorisedWriter {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
||||
defer buf.ReleaseMulti(buffers)
|
||||
return common.Error(t.write(buf.ToSliceMulti(buffers)))
|
||||
@@ -482,7 +461,7 @@ func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
||||
func (t *NativeTun) Close() error {
|
||||
var err error
|
||||
t.closeOnce.Do(func() {
|
||||
atomic.StoreInt32(&t.close, 1)
|
||||
t.close.Store(1)
|
||||
windows.SetEvent(t.readWait)
|
||||
t.running.Wait()
|
||||
t.session.End()
|
||||
@@ -512,24 +491,24 @@ func procyield(cycles uint32)
|
||||
func nanotime() int64
|
||||
|
||||
type rateJuggler struct {
|
||||
current uint64
|
||||
nextByteCount uint64
|
||||
nextStartTime int64
|
||||
changing int32
|
||||
current atomic.Uint64
|
||||
nextByteCount atomic.Uint64
|
||||
nextStartTime atomic.Int64
|
||||
changing atomic.Int32
|
||||
}
|
||||
|
||||
func (rate *rateJuggler) update(packetLen uint64) {
|
||||
now := nanotime()
|
||||
total := atomic.AddUint64(&rate.nextByteCount, packetLen)
|
||||
period := uint64(now - atomic.LoadInt64(&rate.nextStartTime))
|
||||
total := rate.nextByteCount.Add(packetLen)
|
||||
period := uint64(now - rate.nextStartTime.Load())
|
||||
if period >= rateMeasurementGranularity {
|
||||
if !atomic.CompareAndSwapInt32(&rate.changing, 0, 1) {
|
||||
if !rate.changing.CompareAndSwap(0, 1) {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt64(&rate.nextStartTime, now)
|
||||
atomic.StoreUint64(&rate.current, total*uint64(time.Second/time.Nanosecond)/period)
|
||||
atomic.StoreUint64(&rate.nextByteCount, 0)
|
||||
atomic.StoreInt32(&rate.changing, 0)
|
||||
rate.nextStartTime.Store(now)
|
||||
rate.current.Store(total * uint64(time.Second/time.Nanosecond) / period)
|
||||
rate.nextByteCount.Store(0)
|
||||
rate.changing.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ func (e *WintunEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityNone
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
@@ -99,7 +99,11 @@ func (e *WintunEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) AddHeader(buffer stack.PacketBufferPtr) {
|
||||
func (e *WintunEndpoint) AddHeader(buffer *stack.PacketBuffer) {
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||
|
||||
Reference in New Issue
Block a user