Compare commits
28 Commits
v0.8.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0329538ecd | ||
|
|
caaf8469e0 | ||
|
|
c81ce6d358 | ||
|
|
90ea7a0b69 | ||
|
|
6ee3db839d | ||
|
|
1fb8456021 | ||
|
|
5715a3919a | ||
|
|
2a33d64abc | ||
|
|
3144f43fc2 | ||
|
|
635920688d | ||
|
|
2e21cd99ef | ||
|
|
983b3caf40 | ||
|
|
1d02d635b9 | ||
|
|
e88ed52dbc | ||
|
|
381bf9d40d | ||
|
|
2377e62d4a | ||
|
|
a23b66f721 | ||
|
|
2f53768be2 | ||
|
|
9d97d95a9c | ||
|
|
525f783d00 | ||
|
|
a5db80d710 | ||
|
|
a850c4f8a1 | ||
|
|
6516c2d8f1 | ||
|
|
e9e3fbf0c1 | ||
|
|
20161f3059 | ||
|
|
b49e63f8ef | ||
|
|
b42efe2516 | ||
|
|
09d1d97313 |
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@@ -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
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
/.idea/
|
||||
/vendor/
|
||||
.DS_Store
|
||||
!/README.md
|
||||
/*.md
|
||||
|
||||
@@ -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
17
go.mod
@@ -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
24
go.sum
@@ -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
238
nfqueue_linux.go
Normal 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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
179
redirect_route_linux.go
Normal 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)
|
||||
}
|
||||
}
|
||||
5
stack.go
5
stack.go
@@ -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 {
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
78
tun.go
78
tun.go
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
107
tun_linux.go
107
tun_linux.go
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user