Compare commits
1 Commits
v0.4.6
...
v0.4.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cb0451034 |
19
.github/renovate.json
vendored
19
.github/renovate.json
vendored
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"commitMessagePrefix": "[dependencies]",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":disableRateLimiting"
|
||||
],
|
||||
"golang": {
|
||||
"enabled": false
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions"
|
||||
}
|
||||
]
|
||||
}
|
||||
44
.github/workflows/debug.yml
vendored
Normal file
44
.github/workflows/debug.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Debug build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/debug.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Debug build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get latest go version
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Add cache to Go proxy
|
||||
run: |
|
||||
version=`git rev-parse HEAD`
|
||||
mkdir build
|
||||
pushd build
|
||||
go mod init build
|
||||
go get -v github.com/sagernet/sing-tun@$version
|
||||
popd
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
go build -v .
|
||||
40
.github/workflows/lint.yml
vendored
40
.github/workflows/lint.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/lint.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
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') }}
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: .
|
||||
112
.github/workflows/test.yml
vendored
112
.github/workflows/test.yml
vendored
@@ -1,112 +0,0 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/debug.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
build_go120:
|
||||
name: Linux (Go 1.20)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.20
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
build_go121:
|
||||
name: Linux (Go 1.21)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.21
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
build_go122:
|
||||
name: Linux (Go 1.22)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.22
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
build_windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
build_darwin:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
continue-on-error: true
|
||||
- name: Build
|
||||
run: |
|
||||
make test
|
||||
@@ -3,18 +3,14 @@ linters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- govet
|
||||
- gci
|
||||
# - gci
|
||||
- staticcheck
|
||||
- paralleltest
|
||||
- ineffassign
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
custom-order: true
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/sagernet/)
|
||||
- default
|
||||
|
||||
run:
|
||||
go: "1.23"
|
||||
# gci:
|
||||
# sections:
|
||||
# - standard
|
||||
# - prefix(github.com/sagernet/sing)
|
||||
# - default
|
||||
staticcheck:
|
||||
go: '1.19'
|
||||
|
||||
4
Makefile
4
Makefile
@@ -5,7 +5,6 @@ build:
|
||||
GOOS=linux GOARCH=arm64 go build -v -tags with_gvisor .
|
||||
GOOS=linux GOARCH=386 go build -v -tags with_gvisor .
|
||||
GOOS=linux GOARCH=arm go build -v -tags with_gvisor .
|
||||
GOOS=android GOARCH=arm64 go build -v -tags with_gvisor .
|
||||
GOOS=windows GOARCH=amd64 go build -v -tags with_gvisor .
|
||||
|
||||
fmt:
|
||||
@@ -28,5 +27,4 @@ lint_install:
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
test:
|
||||
go build -v .
|
||||
#go test -v .
|
||||
go test -v .
|
||||
28
go.mod
28
go.mod
@@ -1,28 +1,20 @@
|
||||
module github.com/sagernet/sing-tun
|
||||
|
||||
go 1.20
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/sagernet/nftables v0.3.0-beta.4
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba
|
||||
github.com/sagernet/sing v0.3.8
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/sys v0.27.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.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/vishvananda/netns v0.0.4 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
)
|
||||
|
||||
49
go.sum
49
go.sum
@@ -3,41 +3,26 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/google/btree v1.1.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/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/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs=
|
||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
|
||||
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.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk=
|
||||
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -3,7 +3,6 @@ package tun
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
@@ -41,7 +40,6 @@ type DefaultInterfaceMonitor interface {
|
||||
}
|
||||
|
||||
type DefaultInterfaceMonitorOptions struct {
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
OverrideAndroidVPN bool
|
||||
UnderNetworkExtension bool
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
continue
|
||||
}
|
||||
vpnEnabled = true
|
||||
if m.overrideAndroidVPN {
|
||||
if m.options.OverrideAndroidVPN {
|
||||
defaultTableIndex = rule.Table
|
||||
break
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
defaultInterface *net.Interface
|
||||
err error
|
||||
)
|
||||
if m.underNetworkExtension {
|
||||
if m.options.UnderNetworkExtension {
|
||||
defaultInterface, err = getDefaultInterfaceBySocket()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -154,9 +154,9 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
if routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
||||
continue
|
||||
}
|
||||
// if routeMessage.Flags&unix.RTF_IFSCOPE != 0 {
|
||||
//continue
|
||||
//}
|
||||
if routeMessage.Flags&unix.RTF_IFSCOPE != 0 {
|
||||
// continue
|
||||
}
|
||||
defaultInterface = routeInterface
|
||||
break
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
@@ -35,13 +37,11 @@ func (m *networkUpdateMonitor) emit() {
|
||||
}
|
||||
|
||||
type defaultInterfaceMonitor struct {
|
||||
interfaceFinder control.InterfaceFinder
|
||||
overrideAndroidVPN bool
|
||||
underNetworkExtension bool
|
||||
options DefaultInterfaceMonitorOptions
|
||||
networkAddresses []networkAddress
|
||||
defaultInterfaceName string
|
||||
defaultInterfaceIndex int
|
||||
androidVPNEnabled bool
|
||||
noRoute bool
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
checkUpdateTimer *time.Timer
|
||||
element *list.Element[NetworkUpdateCallback]
|
||||
@@ -50,11 +50,15 @@ type defaultInterfaceMonitor struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
type networkAddress struct {
|
||||
interfaceName string
|
||||
interfaceIndex int
|
||||
addresses []netip.Prefix
|
||||
}
|
||||
|
||||
func NewDefaultInterfaceMonitor(networkMonitor NetworkUpdateMonitor, logger logger.Logger, options DefaultInterfaceMonitorOptions) (DefaultInterfaceMonitor, error) {
|
||||
return &defaultInterfaceMonitor{
|
||||
interfaceFinder: options.InterfaceFinder,
|
||||
overrideAndroidVPN: options.OverrideAndroidVPN,
|
||||
underNetworkExtension: options.UnderNetworkExtension,
|
||||
options: options,
|
||||
networkMonitor: networkMonitor,
|
||||
defaultInterfaceIndex: -1,
|
||||
logger: logger,
|
||||
@@ -76,25 +80,48 @@ func (m *defaultInterfaceMonitor) delayCheckUpdate() {
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) postCheckUpdate() {
|
||||
err := m.interfaceFinder.Update()
|
||||
err := m.updateInterfaces()
|
||||
if err != nil {
|
||||
m.logger.Error("update interfaces: ", err)
|
||||
}
|
||||
err = m.checkUpdate()
|
||||
if errors.Is(err, ErrNoRoute) {
|
||||
if !m.noRoute {
|
||||
m.noRoute = true
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.emit(EventNoRoute)
|
||||
}
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.emit(EventNoRoute)
|
||||
} else if err != nil {
|
||||
m.logger.Error("check interface: ", err)
|
||||
} else {
|
||||
m.noRoute = false
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) updateInterfaces() error {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var addresses []networkAddress
|
||||
for _, iif := range interfaces {
|
||||
var netAddresses []net.Addr
|
||||
netAddresses, err = iif.Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var address networkAddress
|
||||
address.interfaceName = iif.Name
|
||||
address.interfaceIndex = iif.Index
|
||||
address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) {
|
||||
value, loaded := it.(*net.IPNet)
|
||||
return value, loaded
|
||||
}), func(it *net.IPNet) netip.Prefix {
|
||||
bits, _ := it.Mask.Size()
|
||||
return netip.PrefixFrom(M.AddrFromIP(it.IP), bits)
|
||||
})
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
m.networkAddresses = addresses
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) Close() error {
|
||||
if m.element != nil {
|
||||
m.networkMonitor.UnregisterCallback(m.element)
|
||||
@@ -103,10 +130,10 @@ func (m *defaultInterfaceMonitor) Close() error {
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string {
|
||||
for _, address := range m.interfaceFinder.Interfaces() {
|
||||
for _, prefix := range address.Addresses {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.Name
|
||||
return address.interfaceName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,10 +141,10 @@ func (m *defaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) s
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int {
|
||||
for _, address := range m.interfaceFinder.Interfaces() {
|
||||
for _, prefix := range address.Addresses {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.Index
|
||||
return address.interfaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,10 +152,10 @@ func (m *defaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr)
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) {
|
||||
for _, address := range m.interfaceFinder.Interfaces() {
|
||||
for _, prefix := range address.Addresses {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.Name, address.Index
|
||||
return address.interfaceName, address.interfaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +163,7 @@ func (m *defaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (stri
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) OverrideAndroidVPN() bool {
|
||||
return m.overrideAndroidVPN
|
||||
return m.options.OverrideAndroidVPN
|
||||
}
|
||||
|
||||
func (m *defaultInterfaceMonitor) AndroidVPNEnabled() bool {
|
||||
|
||||
@@ -78,16 +78,12 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if ifrow.Type == winipcfg.IfTypePropVirtual || ifrow.Type == winipcfg.IfTypeSoftwareLoopback {
|
||||
continue
|
||||
}
|
||||
|
||||
iface, err := row.InterfaceLUID.IPInterface(windows.AF_INET)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !iface.Connected {
|
||||
if ifrow.Type == winipcfg.IfTypePropVirtual || ifrow.Type == winipcfg.IfTypeSoftwareLoopback {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
11
packages.go
11
packages.go
@@ -1,6 +1,6 @@
|
||||
package tun
|
||||
|
||||
import "github.com/sagernet/sing/common/logger"
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
type PackageManager interface {
|
||||
Start() error
|
||||
@@ -11,14 +11,7 @@ type PackageManager interface {
|
||||
SharedPackageByID(id uint32) (string, bool)
|
||||
}
|
||||
|
||||
type PackageManagerOptions struct {
|
||||
Callback PackageManagerCallback
|
||||
|
||||
// Logger is the logger to log errors
|
||||
// optional
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type PackageManagerCallback interface {
|
||||
OnPackagesUpdated(packages int, sharedUsers int)
|
||||
E.Handler
|
||||
}
|
||||
|
||||
@@ -2,33 +2,30 @@ package tun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/abx"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
type packageManager struct {
|
||||
callback PackageManagerCallback
|
||||
logger logger.Logger
|
||||
watcher *fswatch.Watcher
|
||||
watcher *fsnotify.Watcher
|
||||
idByPackage map[string]uint32
|
||||
sharedByPackage map[string]uint32
|
||||
packageById map[uint32]string
|
||||
sharedById map[uint32]string
|
||||
}
|
||||
|
||||
func NewPackageManager(options PackageManagerOptions) (PackageManager, error) {
|
||||
return &packageManager{
|
||||
callback: options.Callback,
|
||||
logger: options.Logger,
|
||||
}, nil
|
||||
func NewPackageManager(callback PackageManagerCallback) (PackageManager, error) {
|
||||
return &packageManager{callback: callback}, nil
|
||||
}
|
||||
|
||||
func (m *packageManager) Start() error {
|
||||
@@ -38,33 +35,42 @@ func (m *packageManager) Start() error {
|
||||
}
|
||||
err = m.startWatcher()
|
||||
if err != nil {
|
||||
m.logger.Error(E.Cause(err, "create watcher for packages list"))
|
||||
m.callback.NewError(context.Background(), E.Cause(err, "create fsnotify watcher"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *packageManager) startWatcher() error {
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: []string{"/data/system/packages.xml"},
|
||||
Direct: true,
|
||||
Callback: m.packagesUpdated,
|
||||
Logger: m.logger,
|
||||
})
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
err = watcher.Add("/data/system/packages.xml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.watcher = watcher
|
||||
go m.loopUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *packageManager) packagesUpdated(path string) {
|
||||
err := m.updatePackages()
|
||||
if err != nil {
|
||||
m.logger.Error(E.Cause(err, "update packages"))
|
||||
func (m *packageManager) loopUpdate() {
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-m.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err := m.updatePackages()
|
||||
if err != nil {
|
||||
m.callback.NewError(context.Background(), E.Cause(err, "update packages"))
|
||||
}
|
||||
case err, ok := <-m.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m.callback.NewError(context.Background(), E.Cause(err, "fsnotify error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ package tun
|
||||
|
||||
import "os"
|
||||
|
||||
func NewPackageManager(options PackageManagerOptions) (PackageManager, error) {
|
||||
func NewPackageManager(callback PackageManagerCallback) (PackageManager, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
35
redirect.go
35
redirect.go
@@ -1,35 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAutoRedirectInputMark = 0x2023
|
||||
DefaultAutoRedirectOutputMark = 0x2024
|
||||
)
|
||||
|
||||
type AutoRedirect interface {
|
||||
Start() error
|
||||
Close() error
|
||||
UpdateRouteAddressSet()
|
||||
}
|
||||
|
||||
type AutoRedirectOptions struct {
|
||||
TunOptions *Options
|
||||
Context context.Context
|
||||
Handler Handler
|
||||
Logger logger.Logger
|
||||
NetworkMonitor NetworkUpdateMonitor
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
TableName string
|
||||
DisableNFTables bool
|
||||
CustomRedirectPort func() int
|
||||
RouteAddressSet *[]*netipx.IPSet
|
||||
RouteExcludeAddressSet *[]*netipx.IPSet
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func (r *autoRedirect) setupIPTables() error {
|
||||
if r.enableIPv4 {
|
||||
err := r.setupIPTablesForFamily(r.iptablesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
err := r.setupIPTablesForFamily(r.ip6tablesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) setupIPTablesForFamily(iptablesPath string) error {
|
||||
tableNameInput := r.tableName + "-input"
|
||||
tableNameForward := r.tableName + "-forward"
|
||||
tableNameOutput := r.tableName + "-output"
|
||||
tableNamePreRouteing := r.tableName + "-prerouting"
|
||||
redirectPort := r.redirectPort()
|
||||
// OUTPUT
|
||||
err := r.runShell(iptablesPath, "-t nat -N", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNameOutput,
|
||||
"-p tcp -o", r.tunOptions.Name,
|
||||
"-j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -I OUTPUT -j", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS == "android" {
|
||||
return nil
|
||||
}
|
||||
// INPUT
|
||||
err = r.runShell(iptablesPath, "-N", tableNameInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-A", tableNameInput,
|
||||
"-i", r.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-A", tableNameInput,
|
||||
"-o", r.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-I INPUT -j", tableNameInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FORWARD
|
||||
err = r.runShell(iptablesPath, "-N", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-i", r.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-o", r.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-I FORWARD -j", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// PREROUTING
|
||||
err = r.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if iptablesPath == r.iptablesPath {
|
||||
routeAddress = r.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = r.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = r.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = r.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
if len(routeAddress) > 0 && (len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0) {
|
||||
return E.New("`*_route_address` is conflict with `include_interface` or `include_uid`")
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", r.tunOptions.Name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, address := range routeExcludeAddress {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, name := range r.tunOptions.ExcludeInterface {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uid := range r.tunOptions.ExcludeUID {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !r.tunOptions.EXP_DisableDNSHijack {
|
||||
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
|
||||
return it.Is4() == (iptablesPath == r.iptablesPath)
|
||||
})
|
||||
if !dnsServer.IsValid() {
|
||||
if iptablesPath == r.iptablesPath {
|
||||
if HasNextAddress(r.tunOptions.Inet4Address[0], 1) {
|
||||
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
|
||||
}
|
||||
} else {
|
||||
if HasNextAddress(r.tunOptions.Inet6Address[0], 1) {
|
||||
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
if dnsServer.IsValid() {
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range r.tunOptions.IncludeInterface {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.IncludeUID {
|
||||
for uid := uidRange.Start; uid <= uidRange.End; uid++ {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-m addrtype --dst-type LOCAL -j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range r.tunOptions.IncludeInterface {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.IncludeUID {
|
||||
for uid := uidRange.Start; uid <= uidRange.End; uid++ {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -I PREROUTING -j", tableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupIPTables() {
|
||||
if r.enableIPv4 {
|
||||
r.cleanupIPTablesForFamily(r.iptablesPath)
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
r.cleanupIPTablesForFamily(r.ip6tablesPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupIPTablesForFamily(iptablesPath string) {
|
||||
tableNameInput := r.tableName + "-input"
|
||||
tableNameOutput := r.tableName + "-output"
|
||||
tableNameForward := r.tableName + "-forward"
|
||||
tableNamePreRouteing := r.tableName + "-prerouting"
|
||||
|
||||
_ = r.runShell(iptablesPath, "-t nat -D OUTPUT -j", tableNameOutput)
|
||||
_ = r.runShell(iptablesPath, "-t nat -F", tableNameOutput)
|
||||
_ = r.runShell(iptablesPath, "-t nat -X", tableNameOutput)
|
||||
if runtime.GOOS == "android" {
|
||||
return
|
||||
}
|
||||
|
||||
_ = r.runShell(iptablesPath, "-D INPUT -j", tableNameInput)
|
||||
_ = r.runShell(iptablesPath, "-F", tableNameInput)
|
||||
_ = r.runShell(iptablesPath, "-X", tableNameInput)
|
||||
|
||||
_ = r.runShell(iptablesPath, "-D FORWARD -j", tableNameForward)
|
||||
_ = r.runShell(iptablesPath, "-F", tableNameForward)
|
||||
_ = r.runShell(iptablesPath, "-X", tableNameForward)
|
||||
|
||||
_ = r.runShell(iptablesPath, "-t nat -D PREROUTING -j", tableNamePreRouteing)
|
||||
_ = r.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
|
||||
_ = r.runShell(iptablesPath, "-t nat -X", tableNamePreRouteing)
|
||||
}
|
||||
|
||||
func (r *autoRedirect) runShell(commands ...any) error {
|
||||
commandStr := strings.Join(F.MapToString(commands), " ")
|
||||
var command *exec.Cmd
|
||||
if r.androidSu {
|
||||
command = exec.Command(r.suPath, "-c", commandStr)
|
||||
} else {
|
||||
commandArray := strings.Split(commandStr, " ")
|
||||
command = exec.Command(commandArray[0], commandArray[1:]...)
|
||||
}
|
||||
combinedOutput, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
return E.Extend(err, F.ToString(commandStr, ": ", string(combinedOutput)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type autoRedirect struct {
|
||||
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
|
||||
}
|
||||
|
||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||
r := &autoRedirect{
|
||||
tunOptions: options.TunOptions,
|
||||
ctx: options.Context,
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
networkMonitor: options.NetworkMonitor,
|
||||
interfaceFinder: options.InterfaceFinder,
|
||||
tableName: options.TableName,
|
||||
useNFTables: runtime.GOOS != "android" && !options.DisableNFTables,
|
||||
customRedirectPortFunc: options.CustomRedirectPort,
|
||||
routeAddressSet: options.RouteAddressSet,
|
||||
routeExcludeAddressSet: options.RouteExcludeAddressSet,
|
||||
}
|
||||
var err error
|
||||
if runtime.GOOS == "android" {
|
||||
r.enableIPv4 = true
|
||||
r.iptablesPath = "/system/bin/iptables"
|
||||
userId := os.Getuid()
|
||||
if userId != 0 {
|
||||
r.androidSu = true
|
||||
for _, suPath := range []string{
|
||||
"su",
|
||||
"/system/bin/su",
|
||||
} {
|
||||
r.suPath, err = exec.LookPath(suPath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if r.useNFTables {
|
||||
err = r.initializeNFTables()
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
r.useNFTables = false
|
||||
r.logger.Debug("missing nftables support: ", err)
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.Inet4Address) > 0 {
|
||||
r.enableIPv4 = true
|
||||
if !r.useNFTables {
|
||||
r.iptablesPath, err = exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "iptables is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.Inet6Address) > 0 {
|
||||
r.enableIPv6 = true
|
||||
if !r.useNFTables {
|
||||
r.ip6tablesPath, err = exec.LookPath("ip6tables")
|
||||
if err != nil {
|
||||
if !r.enableIPv4 {
|
||||
return nil, E.Cause(err, "ip6tables is required")
|
||||
} else {
|
||||
r.enableIPv6 = false
|
||||
r.logger.Error("device has no ip6tables nat support: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) Start() error {
|
||||
if r.customRedirectPortFunc != nil {
|
||||
r.customRedirectPort = r.customRedirectPortFunc()
|
||||
}
|
||||
if r.customRedirectPort == 0 {
|
||||
var listenAddr netip.Addr
|
||||
if runtime.GOOS == "android" {
|
||||
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
} else if r.enableIPv6 {
|
||||
listenAddr = netip.IPv6Unspecified()
|
||||
} else {
|
||||
listenAddr = netip.IPv4Unspecified()
|
||||
}
|
||||
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
|
||||
err := server.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start redirect server")
|
||||
}
|
||||
r.redirectServer = server
|
||||
}
|
||||
var err error
|
||||
if r.useNFTables {
|
||||
r.cleanupNFTables()
|
||||
err = r.setupNFTables()
|
||||
} else {
|
||||
r.cleanupIPTables()
|
||||
err = r.setupIPTables()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *autoRedirect) Close() error {
|
||||
if r.useNFTables {
|
||||
r.cleanupNFTables()
|
||||
} else {
|
||||
r.cleanupIPTables()
|
||||
}
|
||||
return common.Close(
|
||||
common.PtrOrNil(r.redirectServer),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *autoRedirect) UpdateRouteAddressSet() {
|
||||
if r.useNFTables {
|
||||
err := r.nftablesUpdateRouteAddressSet()
|
||||
if err != nil {
|
||||
r.logger.Error("update route address set: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *autoRedirect) initializeNFTables() error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
_, err = nft.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.useNFTables = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) redirectPort() uint16 {
|
||||
if r.customRedirectPort > 0 {
|
||||
return uint16(r.customRedirectPort)
|
||||
}
|
||||
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (r *autoRedirect) setupNFTables() error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
|
||||
table := nft.AddTable(&nftables.Table{
|
||||
Name: r.tableName,
|
||||
Family: nftables.TableFamilyINet,
|
||||
})
|
||||
|
||||
err = r.nftablesCreateAddressSets(nft, table, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.interfaceFinder.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.localAddresses = 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()
|
||||
})
|
||||
})
|
||||
err = r.nftablesCreateLocalAddressSets(nft, table, r.localAddresses, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
skipOutput := len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
|
||||
if !skipOutput {
|
||||
chainOutput := nft.AddChain(&nftables.Chain{
|
||||
Name: "output",
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookOutput,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
if r.tunOptions.AutoRedirectMarkMode {
|
||||
err = r.nftablesCreateExcludeRules(nft, table, chainOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.nftablesCreateUnreachable(nft, table, chainOutput)
|
||||
r.nftablesCreateRedirect(nft, table, chainOutput)
|
||||
|
||||
chainOutputUDP := nft.AddChain(&nftables.Chain{
|
||||
Name: "output_udp",
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookOutput,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
Type: nftables.ChainTypeRoute,
|
||||
})
|
||||
err = r.nftablesCreateExcludeRules(nft, table, chainOutputUDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.nftablesCreateUnreachable(nft, table, chainOutputUDP)
|
||||
r.nftablesCreateMark(nft, table, chainOutputUDP)
|
||||
} else {
|
||||
r.nftablesCreateRedirect(nft, table, chainOutput, &expr.Meta{
|
||||
Key: expr.MetaKeyOIFNAME,
|
||||
Register: 1,
|
||||
}, &expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname(r.tunOptions.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
chainPreRouting := nft.AddChain(&nftables.Chain{
|
||||
Name: "prerouting",
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookPrerouting,
|
||||
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 1),
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
err = r.nftablesCreateExcludeRules(nft, table, chainPreRouting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.nftablesCreateUnreachable(nft, table, chainPreRouting)
|
||||
r.nftablesCreateRedirect(nft, table, chainPreRouting)
|
||||
r.nftablesCreateMark(nft, table, chainPreRouting)
|
||||
|
||||
if r.tunOptions.AutoRedirectMarkMode {
|
||||
chainPreRoutingUDP := nft.AddChain(&nftables.Chain{
|
||||
Name: "prerouting_udp",
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookPrerouting,
|
||||
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 2),
|
||||
Type: nftables.ChainTypeFilter,
|
||||
})
|
||||
if r.enableIPv4 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chainPreRoutingUDP, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, false)
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chainPreRoutingUDP, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, false)
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRoutingUDP,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_UDP},
|
||||
},
|
||||
&expr.Ct{
|
||||
Key: expr.CtKeyMARK,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectInputMark),
|
||||
},
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyMARK,
|
||||
Register: 1,
|
||||
SourceRegister: true,
|
||||
},
|
||||
&expr.Counter{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
err = r.configureOpenWRTFirewall4(nft, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = nft.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.networkListener = r.networkMonitor.RegisterCallback(func() {
|
||||
err = r.nftablesUpdateLocalAddressSet()
|
||||
if err != nil {
|
||||
r.logger.Error("update local address set: ", err)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO; test is this works
|
||||
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
|
||||
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()
|
||||
})
|
||||
})
|
||||
if slices.Equal(newLocalAddresses, r.localAddresses) {
|
||||
return nil
|
||||
}
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
table, err := nft.ListTableOfFamily(r.tableName, nftables.TableFamilyINet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.nftablesCreateLocalAddressSets(nft, table, newLocalAddresses, r.localAddresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.localAddresses = newLocalAddresses
|
||||
return nft.Flush()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesUpdateRouteAddressSet() error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
table, err := nft.ListTableOfFamily(r.tableName, nftables.TableFamilyINet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.nftablesCreateAddressSets(nft, table, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nft.Flush()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupNFTables() {
|
||||
if r.networkListener != nil {
|
||||
r.networkMonitor.UnregisterCallback(r.networkListener)
|
||||
}
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nft.DelTable(&nftables.Table{
|
||||
Name: r.tableName,
|
||||
Family: nftables.TableFamilyINet,
|
||||
})
|
||||
common.Must(r.configureOpenWRTFirewall4(nft, true))
|
||||
_ = nft.Flush()
|
||||
_ = nft.CloseLasting()
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
func nftablesIfname(n string) []byte {
|
||||
b := make([]byte, 16)
|
||||
copy(b, n+"\x00")
|
||||
return b
|
||||
}
|
||||
|
||||
func nftablesCreateExcludeDestinationIPSet(
|
||||
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
||||
id uint32, name string, family nftables.TableFamily, invert bool,
|
||||
) {
|
||||
exprs := []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyNFPROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{byte(family)},
|
||||
},
|
||||
}
|
||||
if family == nftables.TableFamilyIPv4 {
|
||||
exprs = append(exprs,
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: 16,
|
||||
Len: 4,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
exprs = append(exprs,
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: 24,
|
||||
Len: 16,
|
||||
},
|
||||
)
|
||||
}
|
||||
exprs = append(exprs,
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: id,
|
||||
SetName: name,
|
||||
Invert: invert,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: exprs,
|
||||
})
|
||||
}
|
||||
|
||||
func nftablesCreateIPSet(
|
||||
nft *nftables.Conn, table *nftables.Table,
|
||||
id uint32, name string, family nftables.TableFamily,
|
||||
setList []*netipx.IPSet, prefixList []netip.Prefix, appendDefault bool, update bool,
|
||||
) (*nftables.Set, error) {
|
||||
var builder netipx.IPSetBuilder
|
||||
for _, prefix := range prefixList {
|
||||
builder.AddPrefix(prefix)
|
||||
}
|
||||
for _, set := range setList {
|
||||
builder.AddSet(set)
|
||||
}
|
||||
ipSet, err := builder.IPSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipRanges := ipSet.Ranges()
|
||||
setElements := make([]nftables.SetElement, 0, len(ipRanges))
|
||||
for _, rr := range ipRanges {
|
||||
if (family == nftables.TableFamilyIPv4) != rr.From().Is4() {
|
||||
continue
|
||||
}
|
||||
endAddr := rr.To().Next()
|
||||
if !endAddr.IsValid() {
|
||||
endAddr = rr.From()
|
||||
}
|
||||
setElements = append(setElements, nftables.SetElement{
|
||||
Key: rr.From().AsSlice(),
|
||||
})
|
||||
setElements = append(setElements, nftables.SetElement{
|
||||
Key: endAddr.AsSlice(),
|
||||
IntervalEnd: true,
|
||||
})
|
||||
}
|
||||
if len(prefixList) == 0 && appendDefault {
|
||||
if family == nftables.TableFamilyIPv4 {
|
||||
setElements = append(setElements, nftables.SetElement{
|
||||
Key: netip.IPv4Unspecified().AsSlice(),
|
||||
}, nftables.SetElement{
|
||||
Key: netip.IPv4Unspecified().AsSlice(),
|
||||
IntervalEnd: true,
|
||||
})
|
||||
} else {
|
||||
setElements = append(setElements, nftables.SetElement{
|
||||
Key: netip.IPv6Unspecified().AsSlice(),
|
||||
}, nftables.SetElement{
|
||||
Key: netip.IPv6Unspecified().AsSlice(),
|
||||
IntervalEnd: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
var keyType nftables.SetDatatype
|
||||
if family == nftables.TableFamilyIPv4 {
|
||||
keyType = nftables.TypeIPAddr
|
||||
} else {
|
||||
keyType = nftables.TypeIP6Addr
|
||||
}
|
||||
mySet := &nftables.Set{
|
||||
Table: table,
|
||||
ID: id,
|
||||
Name: name,
|
||||
Interval: true,
|
||||
KeyType: keyType,
|
||||
}
|
||||
if id == 0 {
|
||||
mySet.Anonymous = true
|
||||
mySet.Constant = true
|
||||
}
|
||||
if id == 0 {
|
||||
err := nft.AddSet(mySet, setElements)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mySet, nil
|
||||
} else if update {
|
||||
nft.FlushSet(mySet)
|
||||
} else {
|
||||
err := nft.AddSet(mySet, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for len(setElements) > 0 {
|
||||
toAdd := setElements
|
||||
if len(toAdd) > 1000 {
|
||||
toAdd = toAdd[:1000]
|
||||
}
|
||||
setElements = setElements[len(toAdd):]
|
||||
err := nft.SetAddElements(mySet, toAdd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = nft.Flush()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mySet, nil
|
||||
}
|
||||
@@ -1,687 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
"github.com/sagernet/nftables/userdata"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
//go:linkname allocSetID github.com/sagernet/nftables.allocSetID
|
||||
var allocSetID uint32
|
||||
|
||||
func init() {
|
||||
allocSetID = 6
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateAddressSets(
|
||||
nft *nftables.Conn, table *nftables.Table,
|
||||
update bool,
|
||||
) error {
|
||||
routeAddressSet := *r.routeAddressSet
|
||||
routeExcludeAddressSet := *r.routeExcludeAddressSet
|
||||
if len(routeAddressSet) == 0 && len(routeExcludeAddressSet) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(routeAddressSet) > 0 {
|
||||
if r.enableIPv4 {
|
||||
_, err := nftablesCreateIPSet(nft, table, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, routeAddressSet, nil, true, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
_, err := nftablesCreateIPSet(nft, table, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, routeAddressSet, nil, true, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(routeExcludeAddressSet) > 0 {
|
||||
if r.enableIPv4 {
|
||||
_, err := nftablesCreateIPSet(nft, table, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, routeExcludeAddressSet, nil, false, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
_, err := nftablesCreateIPSet(nft, table, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, routeExcludeAddressSet, nil, false, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateLocalAddressSets(
|
||||
nft *nftables.Conn, table *nftables.Table,
|
||||
localAddresses []netip.Prefix, lastAddresses []netip.Prefix,
|
||||
) error {
|
||||
if r.enableIPv4 {
|
||||
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) {
|
||||
update = true
|
||||
}
|
||||
}
|
||||
if len(lastAddresses) == 0 || update {
|
||||
_, err := nftablesCreateIPSet(nft, table, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, nil, localAddresses4, false, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
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) {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) error {
|
||||
if r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput {
|
||||
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.tunOptions.AutoRedirectOutputMark),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if chain.Hooknum == nftables.ChainHookPrerouting {
|
||||
if len(r.tunOptions.IncludeInterface) > 0 {
|
||||
if len(r.tunOptions.IncludeInterface) > 1 {
|
||||
includeInterface := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
KeyType: nftables.TypeIFName,
|
||||
}
|
||||
err := nft.AddSet(includeInterface, common.Map(r.tunOptions.IncludeInterface, func(it string) nftables.SetElement {
|
||||
return nftables.SetElement{
|
||||
Key: nftablesIfname(it),
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: includeInterface.ID,
|
||||
SetName: includeInterface.Name,
|
||||
Invert: true,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpNeq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname(r.tunOptions.IncludeInterface[0]),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.tunOptions.ExcludeInterface) > 0 {
|
||||
if len(r.tunOptions.ExcludeInterface) > 1 {
|
||||
excludeInterface := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
KeyType: nftables.TypeIFName,
|
||||
}
|
||||
err := nft.AddSet(excludeInterface, common.Map(r.tunOptions.ExcludeInterface, func(it string) nftables.SetElement {
|
||||
return nftables.SetElement{
|
||||
Key: nftablesIfname(it),
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: excludeInterface.ID,
|
||||
SetName: excludeInterface.Name,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname(r.tunOptions.ExcludeInterface[0]),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(r.tunOptions.IncludeUID) > 0 {
|
||||
if len(r.tunOptions.IncludeUID) > 1 || r.tunOptions.IncludeUID[0].Start != r.tunOptions.IncludeUID[0].End {
|
||||
includeUID := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
Interval: true,
|
||||
KeyType: nftables.TypeUID,
|
||||
}
|
||||
err := nft.AddSet(includeUID, common.FlatMap(r.tunOptions.IncludeUID, func(it ranges.Range[uint32]) []nftables.SetElement {
|
||||
return []nftables.SetElement{
|
||||
{
|
||||
Key: binaryutil.NativeEndian.PutUint32(it.Start),
|
||||
},
|
||||
{
|
||||
Key: binaryutil.NativeEndian.PutUint32(it.End + 1),
|
||||
IntervalEnd: true,
|
||||
},
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: includeUID.ID,
|
||||
SetName: includeUID.Name,
|
||||
Invert: true,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
UserData: userdata.AppendString(nil, userdata.TypeComment, "not a bug :("),
|
||||
})
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpNeq,
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint32(r.tunOptions.IncludeUID[0].Start),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.tunOptions.ExcludeUID) > 0 {
|
||||
if len(r.tunOptions.ExcludeUID) > 1 || r.tunOptions.ExcludeUID[0].Start != r.tunOptions.ExcludeUID[0].End {
|
||||
excludeUID := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
Interval: true,
|
||||
KeyType: nftables.TypeUID,
|
||||
}
|
||||
err := nft.AddSet(excludeUID, common.FlatMap(r.tunOptions.ExcludeUID, func(it ranges.Range[uint32]) []nftables.SetElement {
|
||||
return []nftables.SetElement{
|
||||
{
|
||||
Key: binaryutil.NativeEndian.PutUint32(it.Start),
|
||||
},
|
||||
{
|
||||
Key: binaryutil.NativeEndian.PutUint32(it.End + 1),
|
||||
IntervalEnd: true,
|
||||
},
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: excludeUID.ID,
|
||||
SetName: excludeUID.Name,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
UserData: userdata.AppendString(nil, userdata.TypeComment, "not a bug :("),
|
||||
})
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.ExcludeUID[0].Start),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.tunOptions.Inet4RouteAddress) > 0 {
|
||||
inet4RouteAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv4, nil, r.tunOptions.Inet4RouteAddress, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet4RouteAddress.ID, inet4RouteAddress.Name, nftables.TableFamilyIPv4, true)
|
||||
}
|
||||
|
||||
if len(r.tunOptions.Inet6RouteAddress) > 0 {
|
||||
inet6RouteAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv6, nil, r.tunOptions.Inet6RouteAddress, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteAddress.ID, inet6RouteAddress.Name, nftables.TableFamilyIPv6, true)
|
||||
}
|
||||
|
||||
if len(r.tunOptions.Inet4RouteExcludeAddress) > 0 {
|
||||
inet4RouteExcludeAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv4, nil, r.tunOptions.Inet4RouteExcludeAddress, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet4RouteExcludeAddress.ID, inet4RouteExcludeAddress.Name, nftables.TableFamilyIPv4, false)
|
||||
}
|
||||
|
||||
if len(r.tunOptions.Inet6RouteExcludeAddress) > 0 {
|
||||
inet6RouteExcludeAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv6, nil, r.tunOptions.Inet6RouteExcludeAddress, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteExcludeAddress.ID, inet6RouteExcludeAddress.Name, nftables.TableFamilyIPv6, false)
|
||||
}
|
||||
|
||||
if !r.tunOptions.EXP_DisableDNSHijack && ((chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT) ||
|
||||
(r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT)) {
|
||||
if r.enableIPv4 {
|
||||
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.tunOptions.AutoRedirectMarkMode &&
|
||||
((chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeRoute) ||
|
||||
(chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeFilter)) {
|
||||
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_UDP},
|
||||
},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if r.enableIPv4 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, false)
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, false)
|
||||
}
|
||||
|
||||
routeAddressSet := *r.routeAddressSet
|
||||
routeExcludeAddressSet := *r.routeExcludeAddressSet
|
||||
|
||||
if r.enableIPv4 && len(routeAddressSet) > 0 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, true)
|
||||
}
|
||||
|
||||
if r.enableIPv6 && len(routeAddressSet) > 0 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, true)
|
||||
}
|
||||
|
||||
if r.enableIPv4 && len(routeExcludeAddressSet) > 0 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, false)
|
||||
}
|
||||
|
||||
if r.enableIPv6 && len(routeExcludeAddressSet) > 0 {
|
||||
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateMark(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectInputMark),
|
||||
},
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyMARK,
|
||||
Register: 1,
|
||||
SourceRegister: true,
|
||||
},
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyMARK,
|
||||
Register: 1,
|
||||
}, // output meta mark set myMark ct mark set meta mark
|
||||
&expr.Ct{
|
||||
Key: expr.CtKeyMARK,
|
||||
Register: 1,
|
||||
SourceRegister: true,
|
||||
},
|
||||
&expr.Counter{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateRedirect(
|
||||
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
||||
exprs ...expr.Any,
|
||||
) {
|
||||
if r.enableIPv4 && !r.enableIPv6 {
|
||||
exprs = append(exprs,
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyNFPROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{uint8(nftables.TableFamilyIPv4)},
|
||||
})
|
||||
} else if !r.enableIPv4 && r.enableIPv6 {
|
||||
exprs = append(exprs,
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyNFPROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{uint8(nftables.TableFamilyIPv6)},
|
||||
})
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: append(exprs,
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_TCP},
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint16(r.redirectPort()),
|
||||
},
|
||||
&expr.Redir{
|
||||
RegisterProtoMin: 1,
|
||||
Flags: unix.NF_NAT_RANGE_PROTO_SPECIFIED,
|
||||
},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
|
||||
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
||||
family nftables.TableFamily,
|
||||
) error {
|
||||
ipProto := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
KeyType: nftables.TypeInetProto,
|
||||
}
|
||||
err := nft.AddSet(ipProto, []nftables.SetElement{
|
||||
{Key: []byte{unix.IPPROTO_TCP}},
|
||||
{Key: []byte{unix.IPPROTO_UDP}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
|
||||
return it.Is4() == (family == nftables.TableFamilyIPv4)
|
||||
})
|
||||
if !dnsServer.IsValid() {
|
||||
if family == nftables.TableFamilyIPv4 {
|
||||
if HasNextAddress(r.tunOptions.Inet4Address[0], 1) {
|
||||
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
|
||||
}
|
||||
} else {
|
||||
if HasNextAddress(r.tunOptions.Inet6Address[0], 1) {
|
||||
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
if !dnsServer.IsValid() {
|
||||
return nil
|
||||
}
|
||||
exprs := []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyNFPROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{uint8(family)},
|
||||
},
|
||||
}
|
||||
if chain.Hooknum == nftables.ChainHookOutput {
|
||||
// It looks like we can't hijack DNS requests sent to loopback.
|
||||
// https://serverfault.com/questions/363899/iptables-dnat-from-loopback
|
||||
// and tproxy is not available in output
|
||||
exprs = append(exprs,
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyOIFNAME,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpNeq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname("lo"),
|
||||
},
|
||||
)
|
||||
}
|
||||
exprs = append(exprs,
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: ipProto.ID,
|
||||
SetName: ipProto.Name,
|
||||
},
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseTransportHeader,
|
||||
Offset: 2,
|
||||
Len: 2,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint16(53),
|
||||
},
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: dnsServer.AsSlice(),
|
||||
},
|
||||
&expr.NAT{
|
||||
Type: expr.NATTypeDestNAT,
|
||||
Family: uint32(family),
|
||||
RegAddrMin: 1,
|
||||
},
|
||||
&expr.Counter{},
|
||||
)
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: exprs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) nftablesCreateUnreachable(
|
||||
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
||||
) {
|
||||
if (r.enableIPv4 && r.enableIPv6) || !r.tunOptions.StrictRoute {
|
||||
return
|
||||
}
|
||||
var nfProto nftables.TableFamily
|
||||
if r.enableIPv4 {
|
||||
nfProto = nftables.TableFamilyIPv6
|
||||
} else {
|
||||
nfProto = nftables.TableFamilyIPv4
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyNFPROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{uint8(nfProto)},
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictDrop,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func (r *autoRedirect) configureOpenWRTFirewall4(nft *nftables.Conn, cleanup bool) error {
|
||||
tableFW4, err := nft.ListTableOfFamily("fw4", nftables.TableFamilyINet)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !cleanup {
|
||||
ruleIif := &nftables.Rule{
|
||||
Table: tableFW4,
|
||||
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.VerdictAccept,
|
||||
},
|
||||
},
|
||||
}
|
||||
ruleOif := &nftables.Rule{
|
||||
Table: tableFW4,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyOIFNAME,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname(r.tunOptions.Name),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
},
|
||||
},
|
||||
}
|
||||
chainForward := &nftables.Chain{
|
||||
Name: "forward",
|
||||
}
|
||||
ruleIif.Chain = chainForward
|
||||
ruleOif.Chain = chainForward
|
||||
nft.InsertRule(ruleOif)
|
||||
nft.InsertRule(ruleIif)
|
||||
chainInput := &nftables.Chain{
|
||||
Name: "input",
|
||||
}
|
||||
ruleIif.Chain = chainInput
|
||||
ruleOif.Chain = chainInput
|
||||
nft.InsertRule(ruleOif)
|
||||
nft.InsertRule(ruleIif)
|
||||
return nil
|
||||
}
|
||||
for _, chainName := range []string{"input", "forward"} {
|
||||
var rules []*nftables.Rule
|
||||
rules, err = nft.GetRules(tableFW4, &nftables.Chain{
|
||||
Name: chainName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if len(rule.Exprs) != 4 {
|
||||
continue
|
||||
}
|
||||
exprMeta, isMeta := rule.Exprs[0].(*expr.Meta)
|
||||
if !isMeta {
|
||||
continue
|
||||
}
|
||||
if exprMeta.Key != expr.MetaKeyIIFNAME && exprMeta.Key != expr.MetaKeyOIFNAME {
|
||||
continue
|
||||
}
|
||||
exprCmp, isCmp := rule.Exprs[1].(*expr.Cmp)
|
||||
if !isCmp {
|
||||
continue
|
||||
}
|
||||
if !slices.Equal(exprCmp.Data, nftablesIfname(r.tunOptions.Name)) {
|
||||
continue
|
||||
}
|
||||
err = nft.DelRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
const ProtocolRedirect = "redirect"
|
||||
|
||||
type redirectServer struct {
|
||||
ctx context.Context
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
listenAddr netip.Addr
|
||||
listener *net.TCPListener
|
||||
inShutdown atomic.Bool
|
||||
}
|
||||
|
||||
func newRedirectServer(ctx context.Context, handler Handler, logger logger.Logger, listenAddr netip.Addr) *redirectServer {
|
||||
return &redirectServer{
|
||||
ctx: ctx,
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
listenAddr: listenAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *redirectServer) Start() error {
|
||||
var listenConfig net.ListenConfig
|
||||
// listenConfig.KeepAlive = C.TCPKeepAliveInitial
|
||||
listenConfig.KeepAlive = 10 * time.Minute
|
||||
listener, err := listenConfig.Listen(s.ctx, M.NetworkFromNetAddr("tcp", s.listenAddr), M.SocksaddrFrom(s.listenAddr, 0).String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.listener = listener.(*net.TCPListener)
|
||||
go s.loopIn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *redirectServer) Close() error {
|
||||
s.inShutdown.Store(true)
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *redirectServer) loopIn() {
|
||||
for {
|
||||
conn, err := s.listener.AcceptTCP()
|
||||
if err != nil {
|
||||
var netError net.Error
|
||||
//goland:noinspection GoDeprecation
|
||||
//nolint:staticcheck
|
||||
if errors.As(err, &netError) && netError.Temporary() {
|
||||
s.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
if s.inShutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
s.listener.Close()
|
||||
s.logger.Error("serve error: ", err)
|
||||
continue
|
||||
}
|
||||
var metadata M.Metadata
|
||||
metadata.Protocol = ProtocolRedirect
|
||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||
destination, err := control.GetOriginalDestination(conn)
|
||||
if err != nil {
|
||||
_ = conn.SetLinger(0)
|
||||
_ = conn.Close()
|
||||
s.logger.Error("process connection from ", metadata.Source, ": invalid connection: ", err)
|
||||
continue
|
||||
}
|
||||
metadata.Destination = M.SocksaddrFromNetIP(destination).Unwrap()
|
||||
go s.handler.NewConnection(s.ctx, conn, metadata)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
8
stack.go
8
stack.go
@@ -59,14 +59,6 @@ func NewStack(
|
||||
}
|
||||
}
|
||||
|
||||
func HasNextAddress(prefix netip.Prefix, count int) bool {
|
||||
checkAddr := prefix.Addr()
|
||||
for i := 0; i < count; i++ {
|
||||
checkAddr = checkAddr.Next()
|
||||
}
|
||||
return prefix.Contains(checkAddr)
|
||||
}
|
||||
|
||||
func BroadcastAddr(inet4Address []netip.Prefix) netip.Addr {
|
||||
if len(inet4Address) == 0 {
|
||||
return netip.Addr{}
|
||||
|
||||
@@ -152,9 +152,6 @@ func (t *GVisor) Start() error {
|
||||
}
|
||||
|
||||
func (t *GVisor) Close() error {
|
||||
if t.stack == nil {
|
||||
return nil
|
||||
}
|
||||
t.endpoint.Attach(nil)
|
||||
t.stack.Close()
|
||||
for _, endpoint := range t.stack.CleanupEndpoints() {
|
||||
|
||||
@@ -138,6 +138,7 @@ func (w *UDPBackWriter) WritePacket(packetBuffer *buf.Buffer, destination M.Sock
|
||||
TTL: route.DefaultTTL(),
|
||||
TOS: 0,
|
||||
}, packet)
|
||||
|
||||
if err != nil {
|
||||
route.Stats().UDP.PacketSendErrors.Increment()
|
||||
return wrapStackError(err)
|
||||
|
||||
@@ -260,9 +260,6 @@ func (m *Mixed) packetLoop() {
|
||||
}
|
||||
|
||||
func (m *Mixed) Close() error {
|
||||
if m.stack == nil {
|
||||
return nil
|
||||
}
|
||||
m.endpoint.Attach(nil)
|
||||
m.stack.Close()
|
||||
for _, endpoint := range m.stack.CleanupEndpoints() {
|
||||
|
||||
@@ -70,14 +70,14 @@ func NewSystem(options StackOptions) (Stack, error) {
|
||||
interfaceFinder: options.InterfaceFinder,
|
||||
}
|
||||
if len(options.TunOptions.Inet4Address) > 0 {
|
||||
if !HasNextAddress(options.TunOptions.Inet4Address[0], 1) {
|
||||
if options.TunOptions.Inet4Address[0].Bits() == 32 {
|
||||
return nil, E.New("need one more IPv4 address in first prefix for system stack")
|
||||
}
|
||||
stack.inet4ServerAddress = options.TunOptions.Inet4Address[0].Addr()
|
||||
stack.inet4Address = stack.inet4ServerAddress.Next()
|
||||
}
|
||||
if len(options.TunOptions.Inet6Address) > 0 {
|
||||
if !HasNextAddress(options.TunOptions.Inet6Address[0], 1) {
|
||||
if options.TunOptions.Inet6Address[0].Bits() == 128 {
|
||||
return nil, E.New("need one more IPv6 address in first prefix for system stack")
|
||||
}
|
||||
stack.inet6ServerAddress = options.TunOptions.Inet6Address[0].Addr()
|
||||
@@ -120,15 +120,8 @@ func (s *System) start() error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
var tcpListener net.Listener
|
||||
if s.inet4Address.IsValid() {
|
||||
for i := 0; i < 3; i++ {
|
||||
tcpListener, err = listener.Listen(s.ctx, "tcp4", net.JoinHostPort(s.inet4ServerAddress.String(), "0"))
|
||||
if !retryableListenError(err) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
tcpListener, err := listener.Listen(s.ctx, "tcp4", net.JoinHostPort(s.inet4ServerAddress.String(), "0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -137,13 +130,7 @@ func (s *System) start() error {
|
||||
go s.acceptLoop(tcpListener)
|
||||
}
|
||||
if s.inet6Address.IsValid() {
|
||||
for i := 0; i < 3; i++ {
|
||||
tcpListener, err = listener.Listen(s.ctx, "tcp6", net.JoinHostPort(s.inet6ServerAddress.String(), "0"))
|
||||
if !retryableListenError(err) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
tcpListener, err := listener.Listen(s.ctx, "tcp6", net.JoinHostPort(s.inet6ServerAddress.String(), "0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,16 +2,6 @@
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func fixWindowsFirewall() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func retryableListenError(err error) bool {
|
||||
return errors.Is(err, unix.EADDRNOTAVAIL)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/winfw"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func fixWindowsFirewall() error {
|
||||
@@ -26,7 +23,3 @@ func fixWindowsFirewall() error {
|
||||
_, err = winfw.FirewallRuleAddAdvanced(rule)
|
||||
return err
|
||||
}
|
||||
|
||||
func retryableListenError(err error) bool {
|
||||
return errors.Is(err, windows.WSAEADDRNOTAVAIL)
|
||||
}
|
||||
|
||||
65
tun.go
65
tun.go
@@ -41,11 +41,6 @@ type LinuxTUN interface {
|
||||
TXChecksumOffload() bool
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultIPRoute2TableIndex = 2022
|
||||
DefaultIPRoute2RuleIndex = 9000
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
Inet4Address []netip.Prefix
|
||||
@@ -53,14 +48,6 @@ type Options struct {
|
||||
MTU uint32
|
||||
GSO bool
|
||||
AutoRoute bool
|
||||
Inet4Gateway netip.Addr
|
||||
Inet6Gateway netip.Addr
|
||||
DNSServers []netip.Addr
|
||||
IPRoute2TableIndex int
|
||||
IPRoute2RuleIndex int
|
||||
AutoRedirectMarkMode bool
|
||||
AutoRedirectInputMark uint32
|
||||
AutoRedirectOutputMark uint32
|
||||
StrictRoute bool
|
||||
Inet4RouteAddress []netip.Prefix
|
||||
Inet6RouteAddress []netip.Prefix
|
||||
@@ -74,62 +61,12 @@ type Options struct {
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
TableIndex int
|
||||
FileDescriptor int
|
||||
Logger logger.Logger
|
||||
|
||||
// No work for TCP, do not use.
|
||||
_TXChecksumOffload bool
|
||||
|
||||
// For library usages.
|
||||
EXP_DisableDNSHijack bool
|
||||
}
|
||||
|
||||
func (o *Options) Inet4GatewayAddr() netip.Addr {
|
||||
if o.Inet4Gateway.IsValid() {
|
||||
return o.Inet4Gateway
|
||||
}
|
||||
if len(o.Inet4Address) > 0 {
|
||||
switch runtime.GOOS {
|
||||
case "android":
|
||||
case "linux":
|
||||
if HasNextAddress(o.Inet4Address[0], 1) {
|
||||
return o.Inet4Address[0].Addr().Next()
|
||||
}
|
||||
case "darwin":
|
||||
return o.Inet4Address[0].Addr()
|
||||
default:
|
||||
if HasNextAddress(o.Inet4Address[0], 1) {
|
||||
return o.Inet4Address[0].Addr().Next()
|
||||
} else {
|
||||
return o.Inet4Address[0].Addr()
|
||||
}
|
||||
}
|
||||
}
|
||||
return netip.IPv4Unspecified()
|
||||
}
|
||||
|
||||
func (o *Options) Inet6GatewayAddr() netip.Addr {
|
||||
if o.Inet6Gateway.IsValid() {
|
||||
return o.Inet6Gateway
|
||||
}
|
||||
if len(o.Inet6Address) > 0 {
|
||||
switch runtime.GOOS {
|
||||
case "android":
|
||||
case "linux":
|
||||
if HasNextAddress(o.Inet6Address[0], 1) {
|
||||
return o.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
case "darwin":
|
||||
return o.Inet6Address[0].Addr()
|
||||
default:
|
||||
if HasNextAddress(o.Inet6Address[0], 1) {
|
||||
return o.Inet6Address[0].Addr().Next()
|
||||
} else {
|
||||
return o.Inet6Address[0].Addr()
|
||||
}
|
||||
}
|
||||
}
|
||||
return netip.IPv6Unspecified()
|
||||
}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
|
||||
@@ -242,15 +242,11 @@ func configure(tunFd int, ifIndex int, name string, options Options) error {
|
||||
if options.AutoRoute {
|
||||
var routeRanges []netip.Prefix
|
||||
routeRanges, err = options.BuildAutoRouteRanges(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gateway4, gateway6 := options.Inet4GatewayAddr(), options.Inet6GatewayAddr()
|
||||
for _, routeRange := range routeRanges {
|
||||
if routeRange.Addr().Is4() {
|
||||
err = addRoute(routeRange, gateway4)
|
||||
err = addRoute(routeRange, options.Inet4Address[0].Addr())
|
||||
} else {
|
||||
err = addRoute(routeRange, gateway6)
|
||||
err = addRoute(routeRange, options.Inet6Address[0].Addr())
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause(err, "add route: ", routeRange)
|
||||
|
||||
@@ -27,9 +27,6 @@ func (e *DarwinEndpoint) MTU() uint32 {
|
||||
return e.tun.mtu
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) SetMTU(mtu uint32) {
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
@@ -38,9 +35,6 @@ func (e *DarwinEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
@@ -126,9 +120,3 @@ func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) Close() {
|
||||
}
|
||||
|
||||
func (e *DarwinEndpoint) SetOnCloseAction(f func()) {
|
||||
}
|
||||
|
||||
189
tun_linux.go
189
tun_linux.go
@@ -182,7 +182,7 @@ var controlPath string
|
||||
func init() {
|
||||
const defaultTunPath = "/dev/net/tun"
|
||||
const androidTunPath = "/dev/tun"
|
||||
if rw.IsFile(androidTunPath) {
|
||||
if rw.FileExists(androidTunPath) {
|
||||
controlPath = androidTunPath
|
||||
} else {
|
||||
controlPath = defaultTunPath
|
||||
@@ -293,10 +293,10 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.options.IPRoute2TableIndex == 0 {
|
||||
if t.options.TableIndex == 0 {
|
||||
for {
|
||||
t.options.IPRoute2TableIndex = int(rand.Uint32())
|
||||
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{Table: t.options.IPRoute2TableIndex}, netlink.RT_FILTER_TABLE)
|
||||
t.options.TableIndex = int(rand.Uint32())
|
||||
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{Table: t.options.TableIndex}, netlink.RT_FILTER_TABLE)
|
||||
if len(routeList) == 0 || fErr != nil {
|
||||
break
|
||||
}
|
||||
@@ -350,24 +350,20 @@ func (t *NativeTun) routes(tunLink netlink.Link) ([]netlink.Route, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Do not create gateway on linux by default
|
||||
gateway4, gateway6 := t.options.Inet4GatewayAddr(), t.options.Inet6GatewayAddr()
|
||||
return common.Map(routeRanges, func(it netip.Prefix) netlink.Route {
|
||||
var gateway net.IP
|
||||
if it.Addr().Is4() && !gateway4.IsUnspecified() {
|
||||
gateway = gateway4.AsSlice()
|
||||
} else if it.Addr().Is6() && !gateway6.IsUnspecified() {
|
||||
gateway = gateway6.AsSlice()
|
||||
}
|
||||
return netlink.Route{
|
||||
Dst: prefixToIPNet(it),
|
||||
Gw: gateway,
|
||||
LinkIndex: tunLink.Attrs().Index,
|
||||
Table: t.options.IPRoute2TableIndex,
|
||||
Table: t.options.TableIndex,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
const (
|
||||
ruleStart = 9000
|
||||
ruleEnd = ruleStart + 10
|
||||
)
|
||||
|
||||
func (t *NativeTun) nextIndex6() int {
|
||||
ruleList, err := netlink.RuleList(netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
@@ -389,7 +385,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
it := netlink.NewRule()
|
||||
it.Priority = t.nextIndex6()
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
it.OifName = t.options.Name
|
||||
return []*netlink.Rule{it}
|
||||
@@ -415,64 +411,10 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
var it *netlink.Rule
|
||||
|
||||
excludeRanges := t.options.ExcludedRanges()
|
||||
|
||||
ruleStart := t.options.IPRoute2RuleIndex
|
||||
priority := ruleStart
|
||||
priority6 := priority
|
||||
nopPriority := ruleEnd
|
||||
|
||||
if t.options.AutoRedirectMarkMode {
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Mark = t.options.AutoRedirectOutputMark
|
||||
it.MarkSet = true
|
||||
it.Goto = priority + 2
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Mark = t.options.AutoRedirectInputMark
|
||||
it.MarkSet = true
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Mark = t.options.AutoRedirectOutputMark
|
||||
it.MarkSet = true
|
||||
it.Goto = priority6 + 2
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Mark = t.options.AutoRedirectInputMark
|
||||
it.MarkSet = true
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
nopPriority := ruleStart + 10
|
||||
for _, excludeRange := range excludeRanges {
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
@@ -578,7 +520,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it = netlink.NewRule()
|
||||
if t.options.InterfaceMonitor.OverrideAndroidVPN() {
|
||||
it.Mark = protectedFromVPN
|
||||
it.MarkSet = true
|
||||
}
|
||||
it.Mask = protectedFromVPN
|
||||
it.Priority = priority
|
||||
@@ -591,7 +532,6 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it = netlink.NewRule()
|
||||
if t.options.InterfaceMonitor.OverrideAndroidVPN() {
|
||||
it.Mark = protectedFromVPN
|
||||
it.MarkSet = true
|
||||
}
|
||||
it.Mask = protectedFromVPN
|
||||
it.Family = unix.AF_INET6
|
||||
@@ -627,7 +567,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Dst = address.Masked()
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
}
|
||||
@@ -635,7 +575,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
@@ -644,7 +584,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
@@ -659,14 +599,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
}
|
||||
if p4 && !t.options.StrictRoute {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IPProto = syscall.IPPROTO_ICMP
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
@@ -677,8 +610,17 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.SuppressPrefixlen = 0
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
}
|
||||
if p4 && !t.options.StrictRoute {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IPProto = syscall.IPPROTO_ICMP
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
|
||||
if p6 && !t.options.StrictRoute {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
@@ -690,19 +632,11 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
}
|
||||
}
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.IifName = t.options.Name
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority
|
||||
it.Invert = true
|
||||
it.IifName = "lo"
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
|
||||
@@ -710,7 +644,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Priority = priority
|
||||
it.IifName = "lo"
|
||||
it.Src = netip.PrefixFrom(netip.IPv4Unspecified(), 32)
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
|
||||
@@ -719,19 +653,23 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Priority = priority
|
||||
it.IifName = "lo"
|
||||
it.Src = address.Masked()
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
}
|
||||
// priority++
|
||||
priority++
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = t.options.Name
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
for _, address := range t.options.Inet6Address {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = "lo"
|
||||
it.Src = address.Masked()
|
||||
it.Table = t.options.TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
@@ -748,25 +686,8 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Goto = nopPriority
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
priority6++
|
||||
|
||||
for _, address := range t.options.Inet6Address {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = "lo"
|
||||
it.Src = address.Masked()
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
priority6++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
// priority6++
|
||||
}
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
@@ -849,8 +770,6 @@ func (t *NativeTun) unsetRules() error {
|
||||
return err
|
||||
}
|
||||
for _, rule := range ruleList {
|
||||
ruleStart := t.options.IPRoute2RuleIndex
|
||||
ruleEnd := ruleStart + 10
|
||||
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
|
||||
ruleToDel := netlink.NewRule()
|
||||
ruleToDel.Family = rule.Family
|
||||
@@ -883,28 +802,20 @@ func (t *NativeTun) routeUpdate(event int) {
|
||||
}
|
||||
|
||||
func (t *NativeTun) setSearchDomainForSystemdResolved() {
|
||||
if t.options.EXP_DisableDNSHijack {
|
||||
return
|
||||
}
|
||||
ctlPath, err := exec.LookPath("resolvectl")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dnsServer := t.options.DNSServers
|
||||
if len(dnsServer) == 0 {
|
||||
if len(t.options.Inet4Address) > 0 && HasNextAddress(t.options.Inet4Address[0], 1) {
|
||||
dnsServer = append(dnsServer, t.options.Inet4Address[0].Addr().Next())
|
||||
}
|
||||
if len(t.options.Inet6Address) > 0 && HasNextAddress(t.options.Inet6Address[0], 1) {
|
||||
dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next())
|
||||
}
|
||||
var dnsServer []netip.Addr
|
||||
if len(t.options.Inet4Address) > 0 {
|
||||
dnsServer = append(dnsServer, t.options.Inet4Address[0].Addr().Next())
|
||||
}
|
||||
if len(dnsServer) == 0 {
|
||||
return
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next())
|
||||
}
|
||||
go shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run()
|
||||
if t.options.AutoRoute {
|
||||
go shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run()
|
||||
go shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
|
||||
}
|
||||
go func() {
|
||||
_ = shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run()
|
||||
_ = shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run()
|
||||
_ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
|
||||
}()
|
||||
}
|
||||
|
||||
16
tun_rules.go
16
tun_rules.go
@@ -109,11 +109,9 @@ func (o *Options) BuildAutoRouteRanges(underNetworkExtension bool) ([]netip.Pref
|
||||
var inet4Ranges []netip.Prefix
|
||||
if len(o.Inet4RouteAddress) > 0 {
|
||||
inet4Ranges = o.Inet4RouteAddress
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, address := range o.Inet4Address {
|
||||
if address.Bits() < 32 {
|
||||
inet4Ranges = append(inet4Ranges, address.Masked())
|
||||
}
|
||||
for _, address := range o.Inet4Address {
|
||||
if address.Bits() < 32 {
|
||||
inet4Ranges = append(inet4Ranges, netipx.RangeOfPrefix(address).Prefixes()...)
|
||||
}
|
||||
}
|
||||
} else if autoRouteUseSubRanges && !underNetworkExtension {
|
||||
@@ -151,11 +149,9 @@ func (o *Options) BuildAutoRouteRanges(underNetworkExtension bool) ([]netip.Pref
|
||||
var inet6Ranges []netip.Prefix
|
||||
if len(o.Inet6RouteAddress) > 0 {
|
||||
inet6Ranges = o.Inet6RouteAddress
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, address := range o.Inet6Address {
|
||||
if address.Bits() < 32 {
|
||||
inet6Ranges = append(inet6Ranges, address.Masked())
|
||||
}
|
||||
for _, address := range o.Inet6Address {
|
||||
if address.Bits() < 32 {
|
||||
inet6Ranges = append(inet6Ranges, netipx.RangeOfPrefix(address).Prefixes()...)
|
||||
}
|
||||
}
|
||||
} else if autoRouteUseSubRanges && !underNetworkExtension {
|
||||
|
||||
147
tun_windows.go
147
tun_windows.go
@@ -72,22 +72,9 @@ func (t *NativeTun) configure() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 address")
|
||||
}
|
||||
if t.options.AutoRoute && !t.options.EXP_DisableDNSHijack {
|
||||
dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is4)
|
||||
if len(dnsServers) == 0 && HasNextAddress(t.options.Inet4Address[0], 1) {
|
||||
dnsServers = []netip.Addr{t.options.Inet4Address[0].Addr().Next()}
|
||||
}
|
||||
if len(dnsServers) > 0 {
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), dnsServers, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 dns")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), nil, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 dns")
|
||||
}
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), []netip.Addr{t.options.Inet4Address[0].Addr().Next()}, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 dns")
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
@@ -95,43 +82,26 @@ func (t *NativeTun) configure() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 address")
|
||||
}
|
||||
if t.options.AutoRoute && !t.options.EXP_DisableDNSHijack {
|
||||
dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is6)
|
||||
if len(dnsServers) == 0 && HasNextAddress(t.options.Inet6Address[0], 1) {
|
||||
dnsServers = []netip.Addr{t.options.Inet6Address[0].Addr().Next()}
|
||||
}
|
||||
if len(dnsServers) > 0 {
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), dnsServers, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 dns")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), nil, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 dns")
|
||||
}
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), []netip.Addr{t.options.Inet6Address[0].Addr().Next()}, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 dns")
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet4Address) > 0 || len(t.options.Inet6Address) > 0 {
|
||||
_ = luid.DisableDNSRegistration()
|
||||
}
|
||||
if t.options.AutoRoute {
|
||||
gateway4, gateway6 := t.options.Inet4GatewayAddr(), t.options.Inet6GatewayAddr()
|
||||
routeRanges, err := t.options.BuildAutoRouteRanges(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, routeRange := range routeRanges {
|
||||
if routeRange.Addr().Is4() {
|
||||
err = luid.AddRoute(routeRange, gateway4, 0)
|
||||
err = luid.AddRoute(routeRange, netip.IPv4Unspecified(), 0)
|
||||
} else {
|
||||
err = luid.AddRoute(routeRange, gateway6, 0)
|
||||
err = luid.AddRoute(routeRange, netip.IPv6Unspecified(), 0)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = windnsapi.FlushResolverCache()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -314,40 +284,42 @@ func (t *NativeTun) configure() error {
|
||||
}
|
||||
}
|
||||
|
||||
if !t.options.EXP_DisableDNSHijack {
|
||||
blockDNSCondition := make([]winsys.FWPM_FILTER_CONDITION0, 1)
|
||||
blockDNSCondition[0].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT
|
||||
blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL
|
||||
blockDNSCondition[0].ConditionValue.Type = winsys.FWP_UINT16
|
||||
blockDNSCondition[0].ConditionValue.Value = uintptr(uint16(53))
|
||||
blockDNSCondition := make([]winsys.FWPM_FILTER_CONDITION0, 2)
|
||||
blockDNSCondition[0].FieldKey = winsys.FWPM_CONDITION_IP_PROTOCOL
|
||||
blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL
|
||||
blockDNSCondition[0].ConditionValue.Type = winsys.FWP_UINT8
|
||||
blockDNSCondition[0].ConditionValue.Value = uintptr(uint8(winsys.IPPROTO_UDP))
|
||||
blockDNSCondition[1].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT
|
||||
blockDNSCondition[1].MatchType = winsys.FWP_MATCH_EQUAL
|
||||
blockDNSCondition[1].ConditionValue.Type = winsys.FWP_UINT16
|
||||
blockDNSCondition[1].ConditionValue.Value = uintptr(uint16(53))
|
||||
|
||||
blockDNSFilter4 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter4.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter4.NumFilterConditions = 1
|
||||
blockDNSFilter4.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv4 dns")
|
||||
blockDNSFilter4.SubLayerKey = subLayerKey
|
||||
blockDNSFilter4.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V4
|
||||
blockDNSFilter4.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter4.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter4.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter4, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
blockDNSFilter4 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter4.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter4.NumFilterConditions = 2
|
||||
blockDNSFilter4.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv4 dns")
|
||||
blockDNSFilter4.SubLayerKey = subLayerKey
|
||||
blockDNSFilter4.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V4
|
||||
blockDNSFilter4.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter4.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter4.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter4, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
|
||||
blockDNSFilter6 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter6.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter6.NumFilterConditions = 1
|
||||
blockDNSFilter6.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv6 dns")
|
||||
blockDNSFilter6.SubLayerKey = subLayerKey
|
||||
blockDNSFilter6.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V6
|
||||
blockDNSFilter6.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter6.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter6.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter6, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
blockDNSFilter6 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter6.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter6.NumFilterConditions = 2
|
||||
blockDNSFilter6.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv6 dns")
|
||||
blockDNSFilter6.SubLayerKey = subLayerKey
|
||||
blockDNSFilter6.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V6
|
||||
blockDNSFilter6.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter6.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter6.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter6, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,40 +327,7 @@ func (t *NativeTun) configure() error {
|
||||
}
|
||||
|
||||
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
||||
t.running.Add(1)
|
||||
defer t.running.Done()
|
||||
retry:
|
||||
if t.close.Load() == 1 {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
start := nanotime()
|
||||
shouldSpin := t.rate.current.Load() >= spinloopRateThreshold && uint64(start-t.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2
|
||||
for {
|
||||
if t.close.Load() == 1 {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
var packet []byte
|
||||
packet, err = t.session.ReceivePacket()
|
||||
switch err {
|
||||
case nil:
|
||||
n = copy(p, packet)
|
||||
t.session.ReleaseReceivePacket(packet)
|
||||
t.rate.update(uint64(n))
|
||||
return
|
||||
case windows.ERROR_NO_MORE_ITEMS:
|
||||
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
|
||||
windows.WaitForSingleObject(t.readWait, windows.INFINITE)
|
||||
goto retry
|
||||
}
|
||||
procyield(1)
|
||||
continue
|
||||
case windows.ERROR_HANDLE_EOF:
|
||||
return 0, os.ErrClosed
|
||||
case windows.ERROR_INVALID_DATA:
|
||||
return 0, errors.New("send ring corrupt")
|
||||
}
|
||||
return 0, fmt.Errorf("read failed: %w", err)
|
||||
}
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (t *NativeTun) ReadPacket() ([]byte, func(), error) {
|
||||
|
||||
@@ -26,9 +26,6 @@ func (e *WintunEndpoint) MTU() uint32 {
|
||||
return e.tun.options.MTU
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetMTU(mtu uint32) {
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
@@ -37,9 +34,6 @@ func (e *WintunEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
@@ -123,9 +117,3 @@ func (e *WintunEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Close() {
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetOnCloseAction(f func()) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user