Compare commits

...

30 Commits

Author SHA1 Message Date
世界
0329538ecd Fix "Fix auto_redirect dropping SO_BINDTODEVICE traffic" 2026-03-14 20:58:43 +08:00
世界
caaf8469e0 Fix auto_redirect dropping SO_BINDTODEVICE traffic
REDIRECT in the OUTPUT chain rewrites the destination to 127.0.0.1,
then ip_route_me_harder() reroutes with the socket's bound interface
constraint (flowi4_oif). Since 127.0.0.1 is only reachable via lo,
the routing lookup fails and the packet is silently dropped.

Add a fallback routing table with `local 127.0.0.1` entries for each
non-loopback interface. When the local table lookup fails due to OIF
mismatch, the fallback table provides a matching RTN_LOCAL route.
The kernel then overrides dev_out to loopback (route.c:2857), so the
packet is delivered locally to the redirect server as intended.

This fixes NetworkManager connectivity checks and other tools that
use SO_BINDTODEVICE (e.g. curl --interface).
2026-03-11 21:15:11 +08:00
世界
c81ce6d358 Fix darwin batch loop not exit on EBADF 2026-03-05 20:35:38 +08:00
wwqgtxx
90ea7a0b69 Fix udp/icmp not work on gso with mixed stack 2026-03-03 23:48:15 +08:00
世界
6ee3db839d Update dependencies 2026-03-02 06:50:15 +08:00
eronez
1fb8456021 Fix DNS not revert while close tun 2026-03-02 06:46:28 +08:00
世界
5715a3919a Fix nftablesCreateLocalAddressSets 2026-02-23 17:52:46 +08:00
wwqgtxx
2a33d64abc Fix logging maybe panic 2026-02-13 00:28:14 +08:00
wwqgtxx
3144f43fc2 Fix udp/icmp not work on gso with system stack 2026-02-13 00:27:34 +08:00
世界
635920688d Add back random iproute2 table index 2026-02-02 14:15:26 +08:00
世界
2e21cd99ef Revert "Disable rp filter atomically"
This reverts commit 983b3caf40.
2026-02-02 14:15:10 +08:00
世界
983b3caf40 Disable rp filter atomically 2026-02-01 10:46:55 +08:00
世界
1d02d635b9 Fix auto_redirect fallback rule 2026-01-29 13:34:49 +08:00
世界
e88ed52dbc Fix auto_redirect on IPv6-only or IPv4-only servers 2026-01-28 18:27:44 +08:00
世界
381bf9d40d Skip tun interface traffic in prerouting UDP/ICMP chain 2026-01-28 18:27:44 +08:00
世界
2377e62d4a Fix IPv6 ULA addresses excluded from local address set
The filter condition `IsGlobalUnicast() && !IsPrivate()` incorrectly
excluded ULA addresses (fc00::/7) from inet6_local_address_set,
causing DNS hijack to fail for IPv6 ULA addresses.

Fixes: sagernet/sing-box#3698
2026-01-17 20:42:31 +08:00
世界
a23b66f721 Skip strict routing on Windows versions below 10 2026-01-17 18:16:51 +08:00
世界
2f53768be2 Fix darwin batch read not exit on stop 2026-01-15 02:08:56 +08:00
世界
9d97d95a9c Fix TUN interface restart fails with existing addresses 2026-01-15 02:08:55 +08:00
世界
525f783d00 Add EXP_ExternalConfiguration for Tailscale 2026-01-07 14:05:47 +08:00
世界
a5db80d710 Fix nfqueue fallback when kernel module unavailable 2025-12-31 03:47:36 +08:00
世界
a850c4f8a1 Add pre-matching support for auto redirect 2025-12-26 14:44:55 +08:00
世界
6516c2d8f1 Fix race condition in ReadPacket 2025-12-17 19:45:01 +08:00
wwqgtxx
e9e3fbf0c1 Apply ping destination filter for Windows 2025-12-01 08:47:38 +08:00
世界
20161f3059 redirect: Fix compatibility with /product/bin/su 2025-10-21 20:41:16 +08:00
世界
b49e63f8ef Fix compatibility with MPTCP 2025-10-17 16:26:45 +08:00
世界
b42efe2516 Update golangci-lint 2025-09-22 12:51:12 +08:00
世界
09d1d97313 Update gVisor to v20250811.0 2025-09-15 23:55:16 +08:00
世界
67013b321e Fix race codes 2025-09-12 18:02:59 +08:00
世界
0381a06643 ping: Add destination rewriter 2025-09-11 18:51:00 +08:00
25 changed files with 1169 additions and 173 deletions

View File

@@ -20,21 +20,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
go-version: ^1.25
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v8
with:
version: latest
args: .
args: --timeout=30m
install-mode: binary
verify: false

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
/.idea/
/vendor/
.DS_Store
!/README.md
/*.md

View File

@@ -1,20 +1,49 @@
linters:
disable-all: true
enable:
- gofumpt
- govet
- gci
- staticcheck
- paralleltest
- ineffassign
linters-settings:
gci:
custom-order: true
sections:
- standard
- prefix(github.com/sagernet/)
- default
version: "2"
run:
go: "1.23"
go: "1.25"
linters:
default: none
enable:
- govet
- ineffassign
- paralleltest
- staticcheck
settings:
staticcheck:
checks:
- all
- -S1000
- -S1008
- -S1017
- -ST1003
- -QF1001
- -QF1003
- -QF1008
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofumpt
settings:
gci:
sections:
- standard
- prefix(github.com/sagernet/)
- default
custom-order: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

17
go.mod
View File

@@ -1,29 +1,30 @@
module github.com/sagernet/sing-tun
go 1.23.1
go 1.24.7
require (
github.com/florianl/go-nfqueue/v2 v2.0.2
github.com/go-ole/go-ole v1.3.0
github.com/google/btree v1.1.3
github.com/mdlayher/netlink v1.9.0
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/sagernet/nftables v0.3.0-beta.4
github.com/sagernet/sing v0.8.0-beta.1
github.com/sagernet/sing v0.8.0
github.com/stretchr/testify v1.11.1
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/net v0.50.0
golang.org/x/sys v0.41.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/sync v0.7.0 // indirect

24
go.sum
View File

@@ -1,5 +1,7 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=
github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc=
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=
@@ -8,26 +10,32 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 h1:x/t3XqWshOlWqRuumpvbUvjtEr/6mJuBXAVovPefbUg=
github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing v0.8.0-beta.1 h1:tBOdh/K/EBdXWuBxUJsZONyxDzyfzjdCF1Yq57QtpE4=
github.com/sagernet/sing v0.8.0-beta.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/sagernet/sing v0.8.0-beta.2 h1:3khO2eE5LMylD/v47+pnVMtFzl6lBY2v/b/V+79qpsE=
github.com/sagernet/sing v0.8.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.8.0 h1:OwLEwbcYfZHvu4olZVljxxC1XRicBqJ1HfiFr6F2WEE=
github.com/sagernet/sing v0.8.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
@@ -38,11 +46,15 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0J
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

238
nfqueue_linux.go Normal file
View File

@@ -0,0 +1,238 @@
//go:build linux
package tun
import (
"context"
"errors"
"sync/atomic"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/florianl/go-nfqueue/v2"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
const nfqueueMaxPacketLen = 512
type nfqueueHandler struct {
ctx context.Context
cancel context.CancelFunc
handler Handler
logger logger.Logger
nfq *nfqueue.Nfqueue
queue uint16
outputMark uint32
resetMark uint32
closed atomic.Bool
}
type nfqueueOptions struct {
Context context.Context
Handler Handler
Logger logger.Logger
Queue uint16
OutputMark uint32
ResetMark uint32
}
func newNFQueueHandler(options nfqueueOptions) (*nfqueueHandler, error) {
ctx, cancel := context.WithCancel(options.Context)
return &nfqueueHandler{
ctx: ctx,
cancel: cancel,
handler: options.Handler,
logger: options.Logger,
queue: options.Queue,
outputMark: options.OutputMark,
resetMark: options.ResetMark,
}, nil
}
func (h *nfqueueHandler) setVerdict(packetID uint32, verdict int, mark uint32) {
var err error
if mark != 0 {
err = h.nfq.SetVerdictWithOption(packetID, verdict, nfqueue.WithMark(mark))
} else {
err = h.nfq.SetVerdict(packetID, verdict)
}
if err != nil && !h.closed.Load() && h.ctx.Err() == nil {
h.logger.Trace(E.Cause(err, "set verdict"))
}
}
func (h *nfqueueHandler) Start() error {
config := nfqueue.Config{
NfQueue: h.queue,
MaxPacketLen: nfqueueMaxPacketLen,
MaxQueueLen: 4096,
Copymode: nfqueue.NfQnlCopyPacket,
AfFamily: unix.AF_UNSPEC,
Flags: nfqueue.NfQaCfgFlagFailOpen,
}
nfq, err := nfqueue.Open(&config)
if err != nil {
return E.Cause(err, "open nfqueue")
}
if err = nfq.SetOption(netlink.NoENOBUFS, true); err != nil {
nfq.Close()
return E.Cause(err, "set nfqueue option")
}
err = nfq.RegisterWithErrorFunc(h.ctx, h.handlePacket, func(e error) int {
if h.ctx.Err() != nil {
return 1
}
h.logger.Error("nfqueue error: ", e)
return 0
})
if err != nil {
nfq.Close()
return E.Cause(err, "register nfqueue")
}
h.nfq = nfq
return nil
}
func parseIPv6TransportHeader(payload []byte) (transportProto uint8, transportOffset int, ok bool) {
if len(payload) < header.IPv6MinimumSize {
return 0, 0, false
}
ipv6 := header.IPv6(payload)
nextHeader := ipv6.NextHeader()
offset := header.IPv6MinimumSize
for {
switch nextHeader {
case unix.IPPROTO_HOPOPTS,
unix.IPPROTO_ROUTING,
unix.IPPROTO_DSTOPTS:
if len(payload) < offset+2 {
return 0, 0, false
}
nextHeader = payload[offset]
extLen := int(payload[offset+1]+1) * 8
if len(payload) < offset+extLen {
return 0, 0, false
}
offset += extLen
case unix.IPPROTO_FRAGMENT:
if len(payload) < offset+8 {
return 0, 0, false
}
nextHeader = payload[offset]
offset += 8
case unix.IPPROTO_AH:
if len(payload) < offset+2 {
return 0, 0, false
}
nextHeader = payload[offset]
extLen := int(payload[offset+1]+2) * 4
if len(payload) < offset+extLen {
return 0, 0, false
}
offset += extLen
case unix.IPPROTO_NONE:
return 0, 0, false
default:
return nextHeader, offset, true
}
}
}
func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int {
if h.closed.Load() {
return 0
}
if attr.PacketID == nil || attr.Payload == nil {
return 0
}
packetID := *attr.PacketID
payload := *attr.Payload
if len(payload) < header.IPv4MinimumSize {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
var srcAddr, dstAddr M.Socksaddr
var tcpOffset int
version := payload[0] >> 4
if version == 4 {
ipv4 := header.IPv4(payload)
if !ipv4.IsValid(len(payload)) || ipv4.Protocol() != uint8(unix.IPPROTO_TCP) {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
srcAddr = M.SocksaddrFrom(ipv4.SourceAddr(), 0)
dstAddr = M.SocksaddrFrom(ipv4.DestinationAddr(), 0)
tcpOffset = int(ipv4.HeaderLength())
} else if version == 6 {
transportProto, transportOffset, ok := parseIPv6TransportHeader(payload)
if !ok || transportProto != unix.IPPROTO_TCP {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
ipv6 := header.IPv6(payload)
srcAddr = M.SocksaddrFrom(ipv6.SourceAddr(), 0)
dstAddr = M.SocksaddrFrom(ipv6.DestinationAddr(), 0)
tcpOffset = transportOffset
} else {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
if len(payload) < tcpOffset+header.TCPMinimumSize {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
tcp := header.TCP(payload[tcpOffset:])
srcAddr = M.SocksaddrFrom(srcAddr.Addr, tcp.SourcePort())
dstAddr = M.SocksaddrFrom(dstAddr.Addr, tcp.DestinationPort())
flags := tcp.Flags()
if !flags.Contains(header.TCPFlagSyn) || flags.Contains(header.TCPFlagAck) {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
_, pErr := h.handler.PrepareConnection(N.NetworkTCP, srcAddr, dstAddr, nil, 0)
switch {
case errors.Is(pErr, ErrBypass):
h.setVerdict(packetID, nfqueue.NfAccept, h.outputMark)
case errors.Is(pErr, ErrReset):
h.setVerdict(packetID, nfqueue.NfAccept, h.resetMark)
case errors.Is(pErr, ErrDrop):
h.setVerdict(packetID, nfqueue.NfDrop, 0)
default:
h.setVerdict(packetID, nfqueue.NfAccept, 0)
}
return 0
}
func (h *nfqueueHandler) Close() error {
h.closed.Store(true)
h.cancel()
if h.nfq != nil {
h.nfq.Close()
}
return nil
}

View File

@@ -189,7 +189,7 @@ func (d *Destination) WritePacket(packet *buf.Buffer) error {
}
func (d *Destination) needFilter() bool {
return runtime.GOOS != "windows" && !d.conn.isLinuxUnprivileged()
return !d.conn.isLinuxUnprivileged()
}
func (d *Destination) registerRequest(request pingRequest) {

View File

@@ -27,7 +27,7 @@ type GVisorDestination struct {
logger logger.ContextLogger
endpoint tcpip.Endpoint
conn *gonet.TCPConn
rewriter *Rewriter
rewriter *SourceRewriter
timeout time.Duration
}
@@ -76,7 +76,7 @@ func ConnectGVisor(
return nil, gonet.TranslateNetstackError(gErr)
}
endpoint.SocketOptions().SetHeaderIncluded(true)
rewriter := NewRewriter(ctx, logger, bindAddress4, bindAddress6)
rewriter := NewSourceRewriter(ctx, logger, bindAddress4, bindAddress6)
rewriter.CreateSession(tun.DirectRouteSession{Source: sourceAddress, Destination: destinationAddress}, routeContext)
destination := &GVisorDestination{
ctx: ctx,

View File

@@ -0,0 +1,79 @@
package ping
import (
"net/netip"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing/common/buf"
)
type DestinationWriter struct {
tun.DirectRouteDestination
destination netip.Addr
}
func NewDestinationWriter(routeDestination tun.DirectRouteDestination, destination netip.Addr) *DestinationWriter {
return &DestinationWriter{routeDestination, destination}
}
func (w *DestinationWriter) WritePacket(packet *buf.Buffer) error {
var ipHdr header.Network
switch header.IPVersion(packet.Bytes()) {
case header.IPv4Version:
ipHdr = header.IPv4(packet.Bytes())
case header.IPv6Version:
ipHdr = header.IPv6(packet.Bytes())
default:
return w.DirectRouteDestination.WritePacket(packet)
}
ipHdr.SetDestinationAddr(w.destination)
if ipHdr4, isIPv4 := ipHdr.(header.IPv4); isIPv4 {
ipHdr4.SetChecksum(^ipHdr4.CalculateChecksum())
}
if ipHdr.TransportProtocol() == header.ICMPv6ProtocolNumber {
icmpHdr := header.ICMPv6(ipHdr.Payload())
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpHdr,
Src: ipHdr.SourceAddressSlice(),
Dst: ipHdr.DestinationAddressSlice(),
}))
}
return w.DirectRouteDestination.WritePacket(packet)
}
type ContextDestinationWriter struct {
tun.DirectRouteContext
destination netip.Addr
}
func NewContextDestinationWriter(context tun.DirectRouteContext, destination netip.Addr) *ContextDestinationWriter {
return &ContextDestinationWriter{
context, destination,
}
}
func (w *ContextDestinationWriter) WritePacket(packet []byte) error {
var ipHdr header.Network
switch header.IPVersion(packet) {
case header.IPv4Version:
ipHdr = header.IPv4(packet)
case header.IPv6Version:
ipHdr = header.IPv6(packet)
default:
return w.DirectRouteContext.WritePacket(packet)
}
ipHdr.SetSourceAddr(w.destination)
if ipHdr4, isIPv4 := ipHdr.(header.IPv4); isIPv4 {
ipHdr4.SetChecksum(^ipHdr4.CalculateChecksum())
}
if ipHdr.TransportProtocol() == header.ICMPv6ProtocolNumber {
icmpHdr := header.ICMPv6(ipHdr.Payload())
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmpHdr,
Src: ipHdr.SourceAddressSlice(),
Dst: ipHdr.DestinationAddressSlice(),
}))
}
return w.DirectRouteContext.WritePacket(packet)
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/sagernet/sing/common/logger"
)
type Rewriter struct {
type SourceRewriter struct {
ctx context.Context
logger logger.ContextLogger
access sync.RWMutex
@@ -20,8 +20,8 @@ type Rewriter struct {
inet6Address netip.Addr
}
func NewRewriter(ctx context.Context, logger logger.ContextLogger, inet4Address netip.Addr, inet6Address netip.Addr) *Rewriter {
return &Rewriter{
func NewSourceRewriter(ctx context.Context, logger logger.ContextLogger, inet4Address netip.Addr, inet6Address netip.Addr) *SourceRewriter {
return &SourceRewriter{
ctx: ctx,
logger: logger,
sessions: make(map[tun.DirectRouteSession]tun.DirectRouteContext),
@@ -31,19 +31,19 @@ func NewRewriter(ctx context.Context, logger logger.ContextLogger, inet4Address
}
}
func (m *Rewriter) CreateSession(session tun.DirectRouteSession, context tun.DirectRouteContext) {
func (m *SourceRewriter) CreateSession(session tun.DirectRouteSession, context tun.DirectRouteContext) {
m.access.Lock()
m.sessions[session] = context
m.access.Unlock()
}
func (m *Rewriter) DeleteSession(session tun.DirectRouteSession) {
func (m *SourceRewriter) DeleteSession(session tun.DirectRouteSession) {
m.access.Lock()
delete(m.sessions, session)
m.access.Unlock()
}
func (m *Rewriter) RewritePacket(packet []byte) {
func (m *SourceRewriter) RewritePacket(packet []byte) {
var ipHdr header.Network
var bindAddr netip.Addr
switch header.IPVersion(packet) {
@@ -82,7 +82,7 @@ func (m *Rewriter) RewritePacket(packet []byte) {
}
}
func (m *Rewriter) WriteBack(packet []byte) (bool, error) {
func (m *SourceRewriter) WriteBack(packet []byte) (bool, error) {
var ipHdr header.Network
var routeSession tun.DirectRouteSession
switch header.IPVersion(packet) {

View File

@@ -5,7 +5,6 @@ import (
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
"go4.org/netipx"
)
@@ -13,6 +12,8 @@ import (
const (
DefaultAutoRedirectInputMark = 0x2023
DefaultAutoRedirectOutputMark = 0x2024
DefaultAutoRedirectResetMark = 0x2025
DefaultAutoRedirectNFQueue = 100
)
type AutoRedirect interface {
@@ -24,7 +25,7 @@ type AutoRedirect interface {
type AutoRedirectOptions struct {
TunOptions *Options
Context context.Context
Handler N.TCPConnectionHandlerEx
Handler Handler
Logger logger.Logger
NetworkMonitor NetworkUpdateMonitor
InterfaceFinder control.InterfaceFinder

View File

@@ -13,34 +13,37 @@ import (
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"go4.org/netipx"
)
type autoRedirect struct {
tunOptions *Options
ctx context.Context
handler N.TCPConnectionHandlerEx
logger logger.Logger
tableName string
networkMonitor NetworkUpdateMonitor
networkListener *list.Element[NetworkUpdateCallback]
interfaceFinder control.InterfaceFinder
localAddresses []netip.Prefix
customRedirectPortFunc func() int
customRedirectPort int
redirectServer *redirectServer
enableIPv4 bool
enableIPv6 bool
iptablesPath string
ip6tablesPath string
useNFTables bool
androidSu bool
suPath string
routeAddressSet *[]*netipx.IPSet
routeExcludeAddressSet *[]*netipx.IPSet
tunOptions *Options
ctx context.Context
handler Handler
logger logger.Logger
tableName string
networkMonitor NetworkUpdateMonitor
networkListener *list.Element[NetworkUpdateCallback]
interfaceFinder control.InterfaceFinder
localAddresses []netip.Prefix
customRedirectPortFunc func() int
customRedirectPort int
redirectServer *redirectServer
enableIPv4 bool
enableIPv6 bool
iptablesPath string
ip6tablesPath string
useNFTables bool
androidSu bool
suPath string
routeAddressSet *[]*netipx.IPSet
routeExcludeAddressSet *[]*netipx.IPSet
nfqueueHandler *nfqueueHandler
nfqueueEnabled bool
redirectRouteTableIndex int
redirectInterfaces []control.Interface
}
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
@@ -69,6 +72,7 @@ func (r *autoRedirect) Start() error {
r.androidSu = true
for _, suPath := range []string{
"su",
"/product/bin/su",
"/system/bin/su",
} {
r.suPath, err = exec.LookPath(suPath)
@@ -124,15 +128,38 @@ func (r *autoRedirect) Start() error {
listenAddr = netip.IPv4Unspecified()
}
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
err := server.Start()
err = server.Start()
if err != nil {
return E.Cause(err, "start redirect server")
}
r.redirectServer = server
}
if r.useNFTables {
var handler *nfqueueHandler
handler, err = newNFQueueHandler(nfqueueOptions{
Context: r.ctx,
Handler: r.handler,
Logger: r.logger,
Queue: r.effectiveNFQueue(),
OutputMark: r.effectiveOutputMark(),
ResetMark: r.effectiveResetMark(),
})
if err != nil {
r.logger.Warn("nfqueue not available, pre-match disabled (missing nfnetlink_queue and nft_queue kernel module?): ", err)
} else if err = handler.Start(); err != nil {
r.logger.Warn("nfqueue start failed, pre-match disabled (missing nfnetlink_queue and nft_queue kernel module?): ", err)
} else {
r.nfqueueHandler = handler
r.nfqueueEnabled = true
}
r.cleanupNFTables()
err = r.setupNFTables()
if err == nil && r.tunOptions.AutoRedirectMarkMode {
err = r.setupRedirectRoutes()
if err != nil {
r.cleanupNFTables()
}
}
} else {
r.cleanupIPTables()
err = r.setupIPTables()
@@ -141,7 +168,11 @@ func (r *autoRedirect) Start() error {
}
func (r *autoRedirect) Close() error {
if r.nfqueueHandler != nil {
r.nfqueueHandler.Close()
}
if r.useNFTables {
r.cleanupRedirectRoutes()
r.cleanupNFTables()
} else {
r.cleanupIPTables()
@@ -180,3 +211,28 @@ func (r *autoRedirect) redirectPort() uint16 {
}
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
}
func (r *autoRedirect) effectiveOutputMark() uint32 {
if r.tunOptions.AutoRedirectOutputMark != 0 {
return r.tunOptions.AutoRedirectOutputMark
}
return DefaultAutoRedirectOutputMark
}
func (r *autoRedirect) effectiveResetMark() uint32 {
if r.tunOptions.AutoRedirectResetMark != 0 {
return r.tunOptions.AutoRedirectResetMark
}
return DefaultAutoRedirectResetMark
}
func (r *autoRedirect) effectiveNFQueue() uint16 {
if r.tunOptions.AutoRedirectNFQueue != 0 {
return r.tunOptions.AutoRedirectNFQueue
}
return DefaultAutoRedirectNFQueue
}
func (r *autoRedirect) shouldSkipOutputChain() bool {
return len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
}

View File

@@ -4,6 +4,7 @@ package tun
import (
"net/netip"
"strings"
"github.com/sagernet/nftables"
"github.com/sagernet/nftables/binaryutil"
@@ -51,13 +52,23 @@ func (r *autoRedirect) setupNFTables() error {
return err
}
skipOutput := len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
if !skipOutput {
if r.nfqueueEnabled {
err = r.nftablesCreatePreMatchChains(nft, table)
if err != nil {
return err
}
}
if !r.shouldSkipOutputChain() {
outputNATPriority := nftables.ChainPriorityMangle
if r.nfqueueEnabled {
outputNATPriority = nftables.ChainPriorityRef(*nftables.ChainPriorityMangle + 1)
}
chainOutput := nft.AddChain(&nftables.Chain{
Name: "output",
Table: table,
Hooknum: nftables.ChainHookOutput,
Priority: nftables.ChainPriorityMangle,
Priority: outputNATPriority,
Type: nftables.ChainTypeNAT,
})
if r.tunOptions.AutoRedirectMarkMode {
@@ -182,6 +193,25 @@ func (r *autoRedirect) setupNFTables() error {
},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRoutingUDP,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyIIFNAME,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: nftablesIfname(r.tunOptions.Name),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRoutingUDP,
@@ -263,12 +293,22 @@ func (r *autoRedirect) setupNFTables() error {
if err != nil {
r.logger.Error("update local address set: ", err)
}
if r.tunOptions.AutoRedirectMarkMode {
err = r.updateRedirectRoutes()
if err != nil {
r.logger.Error("update redirect routes: ", err)
}
}
})
return nil
}
// TODO; test is this works
// TODO: test if this works
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
err := r.interfaceFinder.Update()
if err != nil {
return err
}
newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
@@ -277,6 +317,11 @@ func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
if slices.Equal(newLocalAddresses, r.localAddresses) {
return nil
}
if r.logger != nil {
r.logger.Debug("updating local address set to [", strings.Join(common.Map(newLocalAddresses, func(it netip.Prefix) string {
return it.String()
}), ", ")+"]")
}
nft, err := nftables.New()
if err != nil {
return err
@@ -327,3 +372,122 @@ func (r *autoRedirect) cleanupNFTables() {
_ = nft.Flush()
_ = nft.CloseLasting()
}
func (r *autoRedirect) nftablesCreatePreMatchChains(nft *nftables.Conn, table *nftables.Table) error {
chainPreroutingPreMatch := nft.AddChain(&nftables.Chain{
Name: "prerouting_prematch",
Table: table,
Hooknum: nftables.ChainHookPrerouting,
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest - 1),
Type: nftables.ChainTypeFilter,
})
r.nftablesAddPreMatchRules(nft, table, chainPreroutingPreMatch, true)
if !r.shouldSkipOutputChain() {
chainOutputPreMatch := nft.AddChain(&nftables.Chain{
Name: "output_prematch",
Table: table,
Hooknum: nftables.ChainHookOutput,
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityMangle - 1),
Type: nftables.ChainTypeFilter,
})
r.nftablesAddPreMatchRules(nft, table, chainOutputPreMatch, false)
}
return nil
}
func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain, isPrerouting bool) {
ifnameKey := expr.MetaKeyOIFNAME
if isPrerouting {
ifnameKey = expr.MetaKeyIIFNAME
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: ifnameKey, Register: 1},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: nftablesIfname(r.tunOptions.Name)},
&expr.Verdict{Kind: expr.VerdictReturn},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},
&expr.Verdict{Kind: expr.VerdictReturn},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())},
&expr.Verdict{Kind: expr.VerdictReturn},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Ct{Key: expr.CtKeyMARK, Register: 1},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())},
&expr.Verdict{Kind: expr.VerdictReturn},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Payload{
OperationType: expr.PayloadLoad,
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 13,
Len: 1,
},
&expr.Bitwise{
SourceRegister: 1,
DestRegister: 1,
Len: 1,
Mask: []byte{0x12},
Xor: []byte{0x00},
},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{0x02}},
&expr.Counter{},
&expr.Queue{
Num: r.effectiveNFQueue(),
Flag: expr.QueueFlagBypass,
},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveResetMark())},
&expr.Counter{},
&expr.Reject{Type: unix.NFT_REJECT_TCP_RST},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())},
&expr.Ct{Key: expr.CtKeyMARK, Register: 1, SourceRegister: true},
&expr.Counter{},
},
})
}

View File

@@ -74,12 +74,11 @@ func (r *autoRedirect) nftablesCreateLocalAddressSets(
localAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
updateAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
var update bool
if len(lastAddresses) != 0 {
if !slices.Equal(localAddresses4, updateAddresses4) {
if !slices.Equal(localAddresses4, common.Filter(lastAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})) {
update = true
}
}
@@ -94,19 +93,14 @@ func (r *autoRedirect) nftablesCreateLocalAddressSets(
localAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
updateAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
var update bool
if len(lastAddresses) != 0 {
if !slices.Equal(localAddresses6, updateAddresses6) {
if !slices.Equal(localAddresses6, common.Filter(lastAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})) {
update = true
}
}
localAddresses6 = common.Filter(localAddresses6, func(it netip.Prefix) bool {
address := it.Addr()
return address.IsLoopback() || address.IsGlobalUnicast() && !address.IsPrivate()
})
if len(lastAddresses) == 0 || update {
_, err := nftablesCreateIPSet(nft, table, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, nil, localAddresses6, false, update)
if err != nil {
@@ -213,6 +207,48 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
})
}
}
if r.nfqueueEnabled && chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Ct{
Key: expr.CtKeyMARK,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark()),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
if r.nfqueueEnabled && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Ct{
Key: expr.CtKeyMARK,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark()),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
if chain.Hooknum == nftables.ChainHookPrerouting {
nft.AddRule(&nftables.Rule{
Table: table,
@@ -534,6 +570,43 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false)
}
mptcpVerdict := expr.VerdictDrop
if r.tunOptions.ExcludeMPTCP {
mptcpVerdict = expr.VerdictReturn
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyL4PROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{unix.IPPROTO_TCP},
},
&expr.Exthdr{
DestRegister: 1,
Type: 30,
Offset: 0,
Len: 1,
Flags: unix.NFT_EXTHDR_F_PRESENT,
Op: expr.ExthdrOpTcpopt,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{1},
},
&expr.Counter{},
&expr.Verdict{
Kind: mptcpVerdict,
},
},
})
return nil
}

179
redirect_route_linux.go Normal file
View File

@@ -0,0 +1,179 @@
//go:build linux
package tun
import (
"math/rand"
"net"
"net/netip"
"github.com/sagernet/netlink"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
"golang.org/x/sys/unix"
)
const redirectRouteRulePriority = 1
func (r *autoRedirect) setupRedirectRoutes() error {
for {
r.redirectRouteTableIndex = int(rand.Uint32())
if r.redirectRouteTableIndex == r.tunOptions.IPRoute2TableIndex {
continue
}
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL,
&netlink.Route{Table: r.redirectRouteTableIndex},
netlink.RT_FILTER_TABLE)
if len(routeList) == 0 || fErr != nil {
break
}
}
err := r.interfaceFinder.Update()
if err != nil {
return err
}
tunName := r.tunOptions.Name
r.redirectInterfaces = common.Filter(r.interfaceFinder.Interfaces(), func(it control.Interface) bool {
return it.Name != "lo" && it.Name != tunName && it.Flags&net.FlagUp != 0
})
r.cleanupRedirectRoutes()
for _, iface := range r.redirectInterfaces {
err = r.addRedirectRoutes(iface)
if err != nil {
return err
}
}
if r.enableIPv4 {
rule := netlink.NewRule()
rule.Priority = redirectRouteRulePriority
rule.Table = r.redirectRouteTableIndex
rule.Family = unix.AF_INET
err = netlink.RuleAdd(rule)
if err != nil {
return err
}
}
if r.enableIPv6 {
rule := netlink.NewRule()
rule.Priority = redirectRouteRulePriority
rule.Table = r.redirectRouteTableIndex
rule.Family = unix.AF_INET6
err = netlink.RuleAdd(rule)
if err != nil {
return err
}
}
return nil
}
func (r *autoRedirect) addRedirectRoutes(iface control.Interface) error {
if r.enableIPv4 && common.Any(iface.Addresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
}) {
err := netlink.RouteAppend(&netlink.Route{
LinkIndex: iface.Index,
Dst: &net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.CIDRMask(32, 32)},
Table: r.redirectRouteTableIndex,
Type: unix.RTN_LOCAL,
Scope: netlink.SCOPE_HOST,
})
if err != nil {
return err
}
}
if r.enableIPv6 && common.Any(iface.Addresses, func(it netip.Prefix) bool {
return it.Addr().Is6() && !it.Addr().Is4In6()
}) {
err := netlink.RouteAppend(&netlink.Route{
LinkIndex: iface.Index,
Dst: &net.IPNet{IP: net.IPv6loopback, Mask: net.CIDRMask(128, 128)},
Table: r.redirectRouteTableIndex,
Type: unix.RTN_LOCAL,
Scope: netlink.SCOPE_HOST,
})
if err != nil {
return err
}
}
return nil
}
func (r *autoRedirect) removeRedirectRoutes(linkIndex int) {
if r.enableIPv4 {
_ = netlink.RouteDel(&netlink.Route{
LinkIndex: linkIndex,
Dst: &net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.CIDRMask(32, 32)},
Table: r.redirectRouteTableIndex,
Type: unix.RTN_LOCAL,
})
}
if r.enableIPv6 {
_ = netlink.RouteDel(&netlink.Route{
LinkIndex: linkIndex,
Dst: &net.IPNet{IP: net.IPv6loopback, Mask: net.CIDRMask(128, 128)},
Table: r.redirectRouteTableIndex,
Type: unix.RTN_LOCAL,
})
}
}
func (r *autoRedirect) updateRedirectRoutes() error {
err := r.interfaceFinder.Update()
if err != nil {
return err
}
tunName := r.tunOptions.Name
newInterfaces := common.Filter(r.interfaceFinder.Interfaces(), func(it control.Interface) bool {
return it.Name != "lo" && it.Name != tunName && it.Flags&net.FlagUp != 0
})
oldMap := make(map[int]bool, len(r.redirectInterfaces))
for _, iface := range r.redirectInterfaces {
oldMap[iface.Index] = true
}
newMap := make(map[int]bool, len(newInterfaces))
for _, iface := range newInterfaces {
newMap[iface.Index] = true
}
for _, iface := range newInterfaces {
if !oldMap[iface.Index] {
err = r.addRedirectRoutes(iface)
if err != nil {
return err
}
}
}
for _, iface := range r.redirectInterfaces {
if !newMap[iface.Index] {
r.removeRedirectRoutes(iface.Index)
}
}
r.redirectInterfaces = newInterfaces
return nil
}
func (r *autoRedirect) cleanupRedirectRoutes() {
if r.redirectRouteTableIndex == 0 {
return
}
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL,
&netlink.Route{Table: r.redirectRouteTableIndex},
netlink.RT_FILTER_TABLE)
for _, route := range routes {
_ = netlink.RouteDel(&route)
}
if r.enableIPv4 {
rule := netlink.NewRule()
rule.Priority = redirectRouteRulePriority
rule.Table = r.redirectRouteTableIndex
rule.Family = unix.AF_INET
_ = netlink.RuleDel(rule)
}
if r.enableIPv6 {
rule := netlink.NewRule()
rule.Priority = redirectRouteRulePriority
rule.Table = r.redirectRouteTableIndex
rule.Family = unix.AF_INET6
_ = netlink.RuleDel(rule)
}
}

View File

@@ -13,8 +13,9 @@ import (
)
var (
ErrDrop = E.New("drop by rule")
ErrReset = E.New("reset by rule")
ErrDrop = E.New("drop by rule")
ErrReset = E.New("reset by rule")
ErrBypass = E.New("bypass by rule")
)
type Stack interface {

View File

@@ -3,6 +3,9 @@
package tun
import (
"errors"
"syscall"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
gHdr "github.com/sagernet/gvisor/pkg/tcpip/header"
@@ -169,7 +172,7 @@ func (m *Mixed) batchLoopDarwin(darwinTUN DarwinTUN) {
for {
buffers, err := darwinTUN.BatchRead()
if err != nil {
if E.IsClosed(err) {
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
return
}
m.logger.Error(E.Cause(err, "batch read packet"))

View File

@@ -269,7 +269,7 @@ func (s *System) batchLoopDarwin(darwinTUN DarwinTUN) {
for {
buffers, err := darwinTUN.BatchRead()
if err != nil {
if E.IsClosed(err) {
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
return
}
s.logger.Error(E.Cause(err, "batch read packet"))

View File

@@ -11,6 +11,7 @@ import (
)
type TCPNat struct {
timeout time.Duration
portIndex uint16
portAccess sync.RWMutex
addrAccess sync.RWMutex
@@ -19,6 +20,7 @@ type TCPNat struct {
}
type TCPSession struct {
sync.Mutex
Source netip.AddrPort
Destination netip.AddrPort
LastActive time.Time
@@ -26,38 +28,41 @@ type TCPSession struct {
func NewNat(ctx context.Context, timeout time.Duration) *TCPNat {
natMap := &TCPNat{
timeout: timeout,
portIndex: 10000,
addrMap: make(map[netip.AddrPort]uint16),
portMap: make(map[uint16]*TCPSession),
}
go natMap.loopCheckTimeout(ctx, timeout)
go natMap.loopCheckTimeout(ctx)
return natMap
}
func (n *TCPNat) loopCheckTimeout(ctx context.Context, timeout time.Duration) {
ticker := time.NewTicker(timeout)
func (n *TCPNat) loopCheckTimeout(ctx context.Context) {
ticker := time.NewTicker(n.timeout)
defer ticker.Stop()
for {
select {
case <-ticker.C:
n.checkTimeout(timeout)
n.checkTimeout()
case <-ctx.Done():
return
}
}
}
func (n *TCPNat) checkTimeout(timeout time.Duration) {
func (n *TCPNat) checkTimeout() {
now := time.Now()
n.portAccess.Lock()
defer n.portAccess.Unlock()
n.addrAccess.Lock()
defer n.addrAccess.Unlock()
for natPort, session := range n.portMap {
if now.Sub(session.LastActive) > timeout {
session.Lock()
if now.Sub(session.LastActive) > n.timeout {
delete(n.addrMap, session.Source)
delete(n.portMap, natPort)
}
session.Unlock()
}
}
@@ -66,7 +71,11 @@ func (n *TCPNat) LookupBack(port uint16) *TCPSession {
session := n.portMap[port]
n.portAccess.RUnlock()
if session != nil {
session.LastActive = time.Now()
session.Lock()
if time.Since(session.LastActive) > time.Second {
session.LastActive = time.Now()
}
session.Unlock()
}
return session
}

78
tun.go
View File

@@ -63,50 +63,56 @@ type DarwinTUN interface {
}
const (
DefaultIPRoute2TableIndex = 2022
DefaultIPRoute2RuleIndex = 9000
DefaultIPRoute2TableIndex = 2022
DefaultIPRoute2RuleIndex = 9000
DefaultIPRoute2AutoRedirectFallbackRuleIndex = 32768
)
type Options struct {
Name string
Inet4Address []netip.Prefix
Inet6Address []netip.Prefix
MTU uint32
GSO bool
AutoRoute bool
InterfaceScope bool
Inet4Gateway netip.Addr
Inet6Gateway netip.Addr
DNSServers []netip.Addr
IPRoute2TableIndex int
IPRoute2RuleIndex int
AutoRedirectMarkMode bool
AutoRedirectInputMark uint32
AutoRedirectOutputMark uint32
Inet4LoopbackAddress []netip.Addr
Inet6LoopbackAddress []netip.Addr
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
InterfaceFinder control.InterfaceFinder
InterfaceMonitor DefaultInterfaceMonitor
FileDescriptor int
Logger logger.Logger
Name string
Inet4Address []netip.Prefix
Inet6Address []netip.Prefix
MTU uint32
GSO bool
AutoRoute bool
InterfaceScope bool
Inet4Gateway netip.Addr
Inet6Gateway netip.Addr
DNSServers []netip.Addr
IPRoute2TableIndex int
IPRoute2RuleIndex int
IPRoute2AutoRedirectFallbackRuleIndex int
AutoRedirectMarkMode bool
AutoRedirectInputMark uint32
AutoRedirectOutputMark uint32
AutoRedirectResetMark uint32
AutoRedirectNFQueue uint16
ExcludeMPTCP bool
Inet4LoopbackAddress []netip.Addr
Inet6LoopbackAddress []netip.Addr
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
InterfaceFinder control.InterfaceFinder
InterfaceMonitor DefaultInterfaceMonitor
FileDescriptor int
Logger logger.Logger
// No work for TCP, do not use.
_TXChecksumOffload bool
// For library usages.
EXP_DisableDNSHijack bool
EXP_DisableDNSHijack bool
EXP_ExternalConfiguration bool
// For gvisor stack, it should be enabled when MTU is less than 32768; otherwise it should be less than or equal to 8192.
// The above condition is just an estimate and not exact, calculated on M4 pro.

View File

@@ -146,13 +146,22 @@ func New(options Options) (Tun, error) {
}
func (t *NativeTun) Start() error {
if t.options.EXP_ExternalConfiguration {
return nil
}
t.options.InterfaceMonitor.RegisterMyInterface(t.options.Name)
return t.setRoutes()
}
func (t *NativeTun) Close() error {
if t.options.EXP_ExternalConfiguration {
return t.tunFile.Close()
}
defer flushDNSCache()
return E.Errors(t.unsetRoutes(), t.tunFile.Close())
t.stopFd.Stop()
err := E.Errors(t.unsetRoutes(), t.tunFile.Close())
t.stopFd.Close()
return err
}
func (t *NativeTun) Read(p []byte) (n int, err error) {
@@ -232,6 +241,9 @@ func create(tunFd int, ifIndex int, name string, options Options) error {
if err != nil {
return os.NewSyscallError("IoctlSetIfreqMTU", err)
}
if options.EXP_ExternalConfiguration {
return nil
}
if len(options.Inet4Address) > 0 {
for _, address := range options.Inet4Address {
ifReq := ifAliasReq{
@@ -347,6 +359,9 @@ func (t *NativeTun) BatchRead() ([]*buf.Buffer, error) {
t.buffers = t.buffers[:0]
return nil, errno
}
if n < 0 {
return nil, os.ErrClosed
}
if n < 1 {
return nil, nil
}
@@ -396,11 +411,14 @@ func (t *NativeTun) TXChecksumOffload() bool {
}
func (t *NativeTun) UpdateRouteOptions(tunOptions Options) error {
t.options = tunOptions
if t.options.EXP_ExternalConfiguration {
return nil
}
err := t.unsetRoutes()
if err != nil {
return err
}
t.options = tunOptions
return t.setRoutes()
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
@@ -40,6 +39,7 @@ type NativeTun struct {
writeAccess sync.Mutex
vnetHdr bool
writeBuffer []byte
vnetHdrWriteBuf []byte
gsoToWrite []int
tcpGROTable *tcpGROTable
udpGroAccess sync.Mutex
@@ -125,22 +125,23 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
} else if err != nil {
return err
}
if len(t.options.Inet4Address) > 0 {
for _, address := range t.options.Inet4Address {
addr4, _ := netlink.ParseAddr(address.String())
err = netlink.AddrAdd(tunLink, addr4)
if err != nil {
return err
if !t.options.EXP_ExternalConfiguration {
if len(t.options.Inet4Address) > 0 {
for _, address := range t.options.Inet4Address {
addr4, _ := netlink.ParseAddr(address.String())
err = netlink.AddrAdd(tunLink, addr4)
if err != nil && !errors.Is(err, unix.EEXIST) {
return err
}
}
}
}
if len(t.options.Inet6Address) > 0 {
for _, address := range t.options.Inet6Address {
addr6, _ := netlink.ParseAddr(address.String())
err = netlink.AddrAdd(tunLink, addr6)
if err != nil {
return err
if len(t.options.Inet6Address) > 0 {
for _, address := range t.options.Inet6Address {
addr6, _ := netlink.ParseAddr(address.String())
err = netlink.AddrAdd(tunLink, addr6)
if err != nil && !errors.Is(err, unix.EEXIST) {
return err
}
}
}
}
@@ -148,7 +149,9 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
if t.options.GSO {
err = t.enableGSO()
if err != nil {
t.options.Logger.Warn(err)
if t.options.Logger != nil {
t.options.Logger.Warn(err)
}
}
}
@@ -257,7 +260,9 @@ func (t *NativeTun) Start() error {
if t.options.FileDescriptor != 0 {
return nil
}
t.options.InterfaceMonitor.RegisterMyInterface(t.options.Name)
if !t.options.EXP_ExternalConfiguration {
t.options.InterfaceMonitor.RegisterMyInterface(t.options.Name)
}
tunLink, err := netlink.LinkByName(t.options.Name)
if err != nil {
return err
@@ -273,10 +278,16 @@ func (t *NativeTun) Start() error {
if err != nil {
t.gro.disableTCPGRO()
t.gro.disableUDPGRO()
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
if t.options.Logger != nil {
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
}
}
}
if t.options.EXP_ExternalConfiguration {
return nil
}
if t.options.IPRoute2TableIndex == 0 {
for {
t.options.IPRoute2TableIndex = int(rand.Uint32())
@@ -315,6 +326,11 @@ func (t *NativeTun) Close() error {
if t.interfaceCallback != nil {
t.options.InterfaceMonitor.UnregisterCallback(t.interfaceCallback)
}
if t.options.EXP_ExternalConfiguration {
return common.Close(common.PtrOrNil(t.tunFile))
}
t.unsetSearchDomainForSystemdResolved()
t.unsetAddresses()
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
}
@@ -382,10 +398,7 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
func (t *NativeTun) Write(p []byte) (n int, err error) {
if t.vnetHdr {
buffer := buf.Get(virtioNetHdrLen + len(p))
copy(buffer[virtioNetHdrLen:], p)
_, err = t.BatchWrite([][]byte{buffer}, virtioNetHdrLen)
buf.Put(buffer)
_, err = t.BatchWrite([][]byte{p}, virtioNetHdrLen)
if err != nil {
return
}
@@ -476,6 +489,9 @@ func prefixToIPNet(prefix netip.Prefix) *net.IPNet {
func (t *NativeTun) UpdateRouteOptions(tunOptions Options) error {
if t.options.FileDescriptor > 0 {
return nil
} else if t.options.EXP_ExternalConfiguration {
t.options = tunOptions
return nil
} else if !t.options.AutoRoute {
t.options = tunOptions
return nil
@@ -616,6 +632,22 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.Family = unix.AF_INET6
rules = append(rules, it)
}
// Fallback rules after system default rules (32766: main, 32767: default)
// Only reached when main and default tables have no route
if p4 {
it = netlink.NewRule()
it.Priority = t.options.IPRoute2AutoRedirectFallbackRuleIndex
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET
rules = append(rules, it)
}
if p6 {
it = netlink.NewRule()
it.Priority = t.options.IPRoute2AutoRedirectFallbackRuleIndex
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET6
rules = append(rules, it)
}
return rules
}
@@ -989,7 +1021,7 @@ func (t *NativeTun) unsetRules() error {
for _, rule := range ruleList {
ruleStart := t.options.IPRoute2RuleIndex
ruleEnd := ruleStart + 10
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd || (t.options.AutoRedirectMarkMode && rule.Priority == t.options.IPRoute2AutoRedirectFallbackRuleIndex) {
ruleToDel := netlink.NewRule()
ruleToDel.Family = rule.Family
ruleToDel.Priority = rule.Priority
@@ -1003,6 +1035,24 @@ func (t *NativeTun) unsetRules() error {
return nil
}
func (t *NativeTun) unsetAddresses() {
if t.options.FileDescriptor > 0 {
return
}
tunLink, err := netlink.LinkByName(t.options.Name)
if err != nil {
return
}
for _, address := range t.options.Inet4Address {
addr, _ := netlink.ParseAddr(address.String())
_ = netlink.AddrDel(tunLink, addr)
}
for _, address := range t.options.Inet6Address {
addr, _ := netlink.ParseAddr(address.String())
_ = netlink.AddrDel(tunLink, addr)
}
}
func (t *NativeTun) resetRules() error {
t.unsetRules()
return t.setRules()
@@ -1046,3 +1096,14 @@ func (t *NativeTun) setSearchDomainForSystemdResolved() {
_ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
}()
}
func (t *NativeTun) unsetSearchDomainForSystemdResolved() {
if t.options.EXP_DisableDNSHijack {
return
}
ctlPath, err := exec.LookPath("resolvectl")
if err != nil {
return
}
_ = shell.Exec(ctlPath, "revert", t.options.Name).Run()
}

View File

@@ -3,6 +3,8 @@
package tun
import (
"fmt"
"github.com/sagernet/gvisor/pkg/rawfile"
"github.com/sagernet/gvisor/pkg/tcpip/link/fdbased"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
@@ -18,6 +20,37 @@ var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) WritePacket(pkt *stack.PacketBuffer) (int, error) {
iovecs := t.iovecsOutputDefault
if t.vnetHdr {
if t.vnetHdrWriteBuf == nil {
t.vnetHdrWriteBuf = make([]byte, virtioNetHdrLen)
}
vnetHdr := virtioNetHdr{}
if pkt.GSOOptions.Type != stack.GSONone {
vnetHdr.hdrLen = uint16(pkt.HeaderSize())
if pkt.GSOOptions.NeedsCsum {
vnetHdr.flags = unix.VIRTIO_NET_HDR_F_NEEDS_CSUM
vnetHdr.csumStart = pkt.GSOOptions.L3HdrLen
vnetHdr.csumOffset = pkt.GSOOptions.CsumOffset
}
if uint16(pkt.Data().Size()) > pkt.GSOOptions.MSS {
switch pkt.GSOOptions.Type {
case stack.GSOTCPv4:
vnetHdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV4
case stack.GSOTCPv6:
vnetHdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV6
default:
panic(fmt.Sprintf("Unknown gso type: %v", pkt.GSOOptions.Type))
}
vnetHdr.gsoSize = pkt.GSOOptions.MSS
}
}
if err := vnetHdr.encode(t.vnetHdrWriteBuf); err != nil {
return 0, err
}
iovec := unix.Iovec{Base: &t.vnetHdrWriteBuf[0]}
iovec.SetLen(virtioNetHdrLen)
iovecs = append(iovecs, iovec)
}
var dataLen int
for _, packetSlice := range pkt.AsSlices() {
dataLen += len(packetSlice)

View File

@@ -149,12 +149,12 @@ func (t *tcpGROTable) insert(pkt []byte, srcAddrOffset, dstAddrOffset, tcphOffse
}
func (t *tcpGROTable) updateAt(item tcpGROItem, i int) {
items, _ := t.itemsByFlow[item.key]
items := t.itemsByFlow[item.key]
items[i] = item
}
func (t *tcpGROTable) deleteAt(key tcpFlowKey, i int) {
items, _ := t.itemsByFlow[key]
items := t.itemsByFlow[key]
items = append(items[:i], items[i+1:]...)
t.itemsByFlow[key] = items
}
@@ -254,7 +254,7 @@ func (u *udpGROTable) insert(pkt []byte, srcAddrOffset, dstAddrOffset, udphOffse
}
func (u *udpGROTable) updateAt(item udpGROItem, i int) {
items, _ := u.itemsByFlow[item.key]
items := u.itemsByFlow[item.key]
items[i] = item
}

View File

@@ -65,6 +65,9 @@ func New(options Options) (WinTun, error) {
}
func (t *NativeTun) configure() error {
if t.options.EXP_ExternalConfiguration {
return nil
}
luid := winipcfg.LUID(t.adapter.LUID())
if len(t.options.Inet4Address) > 0 {
err := luid.SetIPAddressesForFamily(winipcfg.AddressFamily(windows.AF_INET), t.options.Inet4Address)
@@ -162,10 +165,10 @@ func (t *NativeTun) Name() (string, error) {
}
func (t *NativeTun) Start() error {
t.options.InterfaceMonitor.RegisterMyInterface(t.options.Name)
if !t.options.AutoRoute {
if t.options.EXP_ExternalConfiguration || !t.options.AutoRoute {
return nil
}
t.options.InterfaceMonitor.RegisterMyInterface(t.options.Name)
luid := winipcfg.LUID(t.adapter.LUID())
gateway4, gateway6 := t.options.Inet4GatewayAddr(), t.options.Inet6GatewayAddr()
routeRanges, err := t.options.BuildAutoRouteRanges(false)
@@ -181,6 +184,13 @@ func (t *NativeTun) Start() error {
return err
}
if t.options.StrictRoute {
major, _, _ := windows.RtlGetNtVersionNumbers()
if major < 10 {
if t.options.Logger != nil {
t.options.Logger.Warn("strict routing is not supported on Windows versions below 10")
}
return nil
}
var engine uintptr
session := &winsys.FWPM_SESSION0{Flags: winsys.FWPM_SESSION_FLAG_DYNAMIC}
err := winsys.FwpmEngineOpen0(nil, winsys.RPC_C_AUTHN_DEFAULT, nil, session, unsafe.Pointer(&engine))
@@ -393,17 +403,33 @@ retry:
}
}
func (t *NativeTun) MTU() (int, error) {
return int(t.options.MTU), nil
}
func (t *NativeTun) ForceMTU(mtu int) {
if mtu <= 0 {
return
}
t.options.MTU = uint32(mtu)
}
func (t *NativeTun) LUID() uint64 {
return t.adapter.LUID()
}
func (t *NativeTun) ReadPacket() ([]byte, func(), error) {
t.running.Add(1)
defer t.running.Done()
retry:
if t.close.Load() == 1 {
t.running.Done()
return nil, nil, os.ErrClosed
}
start := nanotime()
shouldSpin := t.rate.current.Load() >= spinloopRateThreshold && uint64(start-t.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2
for {
if t.close.Load() == 1 {
t.running.Done()
return nil, nil, os.ErrClosed
}
packet, err := t.session.ReceivePacket()
@@ -411,7 +437,10 @@ retry:
case nil:
packetSize := len(packet)
t.rate.update(uint64(packetSize))
return packet, func() { t.session.ReleaseReceivePacket(packet) }, nil
return packet, func() {
t.session.ReleaseReceivePacket(packet)
t.running.Done()
}, nil
case windows.ERROR_NO_MORE_ITEMS:
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
windows.WaitForSingleObject(t.readWait, windows.INFINITE)
@@ -420,10 +449,13 @@ retry:
procyield(1)
continue
case windows.ERROR_HANDLE_EOF:
t.running.Done()
return nil, nil, os.ErrClosed
case windows.ERROR_INVALID_DATA:
t.running.Done()
return nil, nil, errors.New("send ring corrupt")
}
t.running.Done()
return nil, nil, fmt.Errorf("read failed: %w", err)
}
}
@@ -536,6 +568,9 @@ func (t *NativeTun) Close() error {
func (t *NativeTun) UpdateRouteOptions(tunOptions Options) error {
t.options = tunOptions
if t.options.EXP_ExternalConfiguration {
return nil
}
if !t.options.AutoRoute {
return nil
}