Merge remote-tracking branch 'upstream/Alpha' into Alpha
This commit is contained in:
commit
68345b6a19
29
.github/workflows/codeql-analysis.yml
vendored
Normal file
29
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: CodeQL
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ rm ]
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: ['go']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
27
.github/workflows/linter.yml
vendored
Normal file
27
.github/workflows/linter.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: Linter
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- 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 }}
|
||||||
|
with:
|
||||||
|
go-version: 1.18.x
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
#uses: golangci/golangci-lint-action@v3
|
||||||
|
#with:
|
||||||
|
# version: latest
|
||||||
|
run: |
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@ec95236
|
||||||
|
golangci-lint run ./...
|
@ -4,7 +4,9 @@ linters:
|
|||||||
- gofumpt
|
- gofumpt
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- govet
|
- govet
|
||||||
- gci
|
# - gci
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gci:
|
gci:
|
||||||
|
25
Makefile
25
Makefile
@ -14,7 +14,7 @@ PLATFORM_LIST = \
|
|||||||
linux-armv5 \
|
linux-armv5 \
|
||||||
linux-armv6 \
|
linux-armv6 \
|
||||||
linux-armv7 \
|
linux-armv7 \
|
||||||
linux-armv8 \
|
linux-arm64 \
|
||||||
linux-mips64 \
|
linux-mips64 \
|
||||||
linux-mips64le \
|
linux-mips64le \
|
||||||
linux-mips-softfloat \
|
linux-mips-softfloat \
|
||||||
@ -26,20 +26,15 @@ PLATFORM_LIST = \
|
|||||||
freebsd-amd64 \
|
freebsd-amd64 \
|
||||||
freebsd-arm64
|
freebsd-arm64
|
||||||
|
|
||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64 \
|
windows-amd64 \
|
||||||
windows-arm64 \
|
windows-arm64 \
|
||||||
windows-arm32v7
|
windows-arm32v7
|
||||||
|
|
||||||
|
all:linux-amd64 linux-arm64\
|
||||||
all:linux-amd64\
|
|
||||||
linux-armv7\
|
|
||||||
darwin-amd64 darwin-arm64\
|
darwin-amd64 darwin-arm64\
|
||||||
windows-amd64 windows-386 \
|
windows-amd64 windows-arm64 \
|
||||||
linux-mips-hardfloat linux-mips-softfloat linux-mips64 linux-mips64le linux-mipsle-hardfloat linux-mipsle-softfloat# Most used
|
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
@ -55,9 +50,6 @@ linux-386:
|
|||||||
linux-amd64:
|
linux-amd64:
|
||||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-amd64-AutoIptables:
|
|
||||||
GOARCH=amd64 GOOS=linux $(GOBUILDOP) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-arm64:
|
linux-arm64:
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
@ -70,7 +62,7 @@ linux-armv6:
|
|||||||
linux-armv7:
|
linux-armv7:
|
||||||
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-armv8:
|
linux-arm64:
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-mips-softfloat:
|
linux-mips-softfloat:
|
||||||
@ -91,7 +83,7 @@ linux-mips64:
|
|||||||
linux-mips64le:
|
linux-mips64le:
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
android-armv8:
|
android-arm64:
|
||||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
freebsd-386:
|
freebsd-386:
|
||||||
@ -128,5 +120,12 @@ $(zip_releases): %.zip : %
|
|||||||
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||||
|
|
||||||
releases: $(gz_releases) $(zip_releases)
|
releases: $(gz_releases) $(zip_releases)
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm $(BINDIR)/*
|
rm $(BINDIR)/*
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
"github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,6 +47,10 @@ type VmessOption struct {
|
|||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
|
|
||||||
|
// TODO: compatible with VMESS WS older version configurations
|
||||||
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -76,6 +80,13 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
var err error
|
var err error
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
if v.option.WSOpts.Path == "" {
|
||||||
|
v.option.WSOpts.Path = v.option.WSPath
|
||||||
|
}
|
||||||
|
if len(v.option.WSOpts.Headers) == 0 {
|
||||||
|
v.option.WSOpts.Headers = v.option.WSHeaders
|
||||||
|
}
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
|
35
common/cmd/cmd.go
Normal file
35
common/cmd/cmd.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExecCmd(cmdStr string) (string, error) {
|
||||||
|
args := splitArgs(cmdStr)
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if len(args) == 1 {
|
||||||
|
cmd = exec.Command(args[0])
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command(args[0], args[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%v, %s", err, string(out))
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitArgs(cmd string) []string {
|
||||||
|
args := strings.Split(cmd, " ")
|
||||||
|
|
||||||
|
// use in pipeline
|
||||||
|
if len(args) > 2 && strings.ContainsAny(cmd, "|") {
|
||||||
|
suffix := strings.Join(args[2:], " ")
|
||||||
|
args = append(args[:2], suffix)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
40
common/cmd/cmd_test.go
Normal file
40
common/cmd/cmd_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitArgs(t *testing.T) {
|
||||||
|
args := splitArgs("ls")
|
||||||
|
args1 := splitArgs("ls -la")
|
||||||
|
args2 := splitArgs("bash -c ls")
|
||||||
|
args3 := splitArgs("bash -c ls -lahF | grep 'cmd'")
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(args))
|
||||||
|
assert.Equal(t, 2, len(args1))
|
||||||
|
assert.Equal(t, 3, len(args2))
|
||||||
|
assert.Equal(t, 3, len(args3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecCmd(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
_, err := ExecCmd("dir")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ExecCmd("ls")
|
||||||
|
_, err1 := ExecCmd("ls -la")
|
||||||
|
_, err2 := ExecCmd("bash -c ls")
|
||||||
|
_, err3 := ExecCmd("bash -c ls -la")
|
||||||
|
_, err4 := ExecCmd("bash -c ls -la | grep 'cmd'")
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, err1)
|
||||||
|
assert.Nil(t, err2)
|
||||||
|
assert.Nil(t, err3)
|
||||||
|
assert.Nil(t, err4)
|
||||||
|
}
|
28
component/dialer/resolver.go
Normal file
28
component/dialer/resolver.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// We must use this DialContext to query DNS
|
||||||
|
// when using net default resolver.
|
||||||
|
net.DefaultResolver.PreferGo = true
|
||||||
|
net.DefaultResolver.Dial = resolverDialContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolverDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
d := &net.Dialer{}
|
||||||
|
|
||||||
|
interfaceName := DefaultInterface.Load()
|
||||||
|
|
||||||
|
if interfaceName != "" {
|
||||||
|
dstIP := net.ParseIP(address)
|
||||||
|
if dstIP != nil {
|
||||||
|
bindIfaceToDialer(interfaceName, d, network, dstIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
}
|
@ -22,14 +22,15 @@ type store interface {
|
|||||||
|
|
||||||
// Pool is a implementation about fake ip generator without storage
|
// Pool is a implementation about fake ip generator without storage
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
max uint32
|
max uint32
|
||||||
min uint32
|
min uint32
|
||||||
gateway uint32
|
gateway uint32
|
||||||
offset uint32
|
broadcast uint32
|
||||||
mux sync.Mutex
|
offset uint32
|
||||||
host *trie.DomainTrie
|
mux sync.Mutex
|
||||||
ipnet *net.IPNet
|
host *trie.DomainTrie
|
||||||
store store
|
ipnet *net.IPNet
|
||||||
|
store store
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup return a fake ip with host
|
// Lookup return a fake ip with host
|
||||||
@ -82,6 +83,11 @@ func (p *Pool) Gateway() net.IP {
|
|||||||
return uintToIP(p.gateway)
|
return uintToIP(p.gateway)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Broadcast return broadcast ip
|
||||||
|
func (p *Pool) Broadcast() net.IP {
|
||||||
|
return uintToIP(p.broadcast)
|
||||||
|
}
|
||||||
|
|
||||||
// IPNet return raw ipnet
|
// IPNet return raw ipnet
|
||||||
func (p *Pool) IPNet() *net.IPNet {
|
func (p *Pool) IPNet() *net.IPNet {
|
||||||
return p.ipnet
|
return p.ipnet
|
||||||
@ -144,7 +150,7 @@ func New(options Options) (*Pool, error) {
|
|||||||
min := ipToUint(options.IPNet.IP) + 2
|
min := ipToUint(options.IPNet.IP) + 2
|
||||||
|
|
||||||
ones, bits := options.IPNet.Mask.Size()
|
ones, bits := options.IPNet.Mask.Size()
|
||||||
total := 1<<uint(bits-ones) - 2
|
total := 1<<uint(bits-ones) - 3
|
||||||
|
|
||||||
if total <= 0 {
|
if total <= 0 {
|
||||||
return nil, errors.New("ipnet don't have valid ip")
|
return nil, errors.New("ipnet don't have valid ip")
|
||||||
@ -152,11 +158,12 @@ func New(options Options) (*Pool, error) {
|
|||||||
|
|
||||||
max := min + uint32(total) - 1
|
max := min + uint32(total) - 1
|
||||||
pool := &Pool{
|
pool := &Pool{
|
||||||
min: min,
|
min: min,
|
||||||
max: max,
|
max: max,
|
||||||
gateway: min - 1,
|
gateway: min - 1,
|
||||||
host: options.Host,
|
broadcast: max + 1,
|
||||||
ipnet: options.IPNet,
|
host: options.Host,
|
||||||
|
ipnet: options.IPNet,
|
||||||
}
|
}
|
||||||
if options.Persistence {
|
if options.Persistence {
|
||||||
pool.store = &cachefileStore{
|
pool.store = &cachefileStore{
|
||||||
|
@ -87,7 +87,7 @@ func TestPool_CycleUsed(t *testing.T) {
|
|||||||
for _, pool := range pools {
|
for _, pool := range pools {
|
||||||
foo := pool.Lookup("foo.com")
|
foo := pool.Lookup("foo.com")
|
||||||
bar := pool.Lookup("bar.com")
|
bar := pool.Lookup("bar.com")
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
pool.Lookup(fmt.Sprintf("%d.com", i))
|
pool.Lookup(fmt.Sprintf("%d.com", i))
|
||||||
}
|
}
|
||||||
baz := pool.Lookup("baz.com")
|
baz := pool.Lookup("baz.com")
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/geodata/strmatcher"
|
"github.com/Dreamacro/clash/component/geodata/strmatcher"
|
||||||
@ -71,6 +73,241 @@ func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
|||||||
return len(m.matchers.Match(strings.ToLower(domain))) > 0
|
return len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CIDRList is an alias of []*CIDR to provide sort.Interface.
|
||||||
|
type CIDRList []*CIDR
|
||||||
|
|
||||||
|
// Len implements sort.Interface.
|
||||||
|
func (l *CIDRList) Len() int {
|
||||||
|
return len(*l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less implements sort.Interface.
|
||||||
|
func (l *CIDRList) Less(i int, j int) bool {
|
||||||
|
ci := (*l)[i]
|
||||||
|
cj := (*l)[j]
|
||||||
|
|
||||||
|
if len(ci.Ip) < len(cj.Ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ci.Ip) > len(cj.Ip) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := 0; k < len(ci.Ip); k++ {
|
||||||
|
if ci.Ip[k] < cj.Ip[k] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.Ip[k] > cj.Ip[k] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ci.Prefix < cj.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap implements sort.Interface.
|
||||||
|
func (l *CIDRList) Swap(i int, j int) {
|
||||||
|
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipv6 struct {
|
||||||
|
a uint64
|
||||||
|
b uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoIPMatcher struct {
|
||||||
|
countryCode string
|
||||||
|
reverseMatch bool
|
||||||
|
ip4 []uint32
|
||||||
|
prefix4 []uint8
|
||||||
|
ip6 []ipv6
|
||||||
|
prefix6 []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize4(ip uint32, prefix uint8) uint32 {
|
||||||
|
return (ip >> (32 - prefix)) << (32 - prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize6(ip ipv6, prefix uint8) ipv6 {
|
||||||
|
if prefix <= 64 {
|
||||||
|
ip.a = (ip.a >> (64 - prefix)) << (64 - prefix)
|
||||||
|
ip.b = 0
|
||||||
|
} else {
|
||||||
|
ip.b = (ip.b >> (128 - prefix)) << (128 - prefix)
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
|
||||||
|
ip4Count := 0
|
||||||
|
ip6Count := 0
|
||||||
|
|
||||||
|
for _, cidr := range cidrs {
|
||||||
|
ip := cidr.Ip
|
||||||
|
switch len(ip) {
|
||||||
|
case 4:
|
||||||
|
ip4Count++
|
||||||
|
case 16:
|
||||||
|
ip6Count++
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpect ip length: %d", len(ip))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrList := CIDRList(cidrs)
|
||||||
|
sort.Sort(&cidrList)
|
||||||
|
|
||||||
|
m.ip4 = make([]uint32, 0, ip4Count)
|
||||||
|
m.prefix4 = make([]uint8, 0, ip4Count)
|
||||||
|
m.ip6 = make([]ipv6, 0, ip6Count)
|
||||||
|
m.prefix6 = make([]uint8, 0, ip6Count)
|
||||||
|
|
||||||
|
for _, cidr := range cidrs {
|
||||||
|
ip := cidr.Ip
|
||||||
|
prefix := uint8(cidr.Prefix)
|
||||||
|
switch len(ip) {
|
||||||
|
case 4:
|
||||||
|
m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix))
|
||||||
|
m.prefix4 = append(m.prefix4, prefix)
|
||||||
|
case 16:
|
||||||
|
ip6 := ipv6{
|
||||||
|
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||||
|
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||||
|
}
|
||||||
|
ip6 = normalize6(ip6, prefix)
|
||||||
|
|
||||||
|
m.ip6 = append(m.ip6, ip6)
|
||||||
|
m.prefix6 = append(m.prefix6, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
|
||||||
|
m.reverseMatch = isReverseMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GeoIPMatcher) match4(ip uint32) bool {
|
||||||
|
if len(m.ip4) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip < m.ip4[0] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
size := uint32(len(m.ip4))
|
||||||
|
l := uint32(0)
|
||||||
|
r := size
|
||||||
|
for l < r {
|
||||||
|
x := ((l + r) >> 1)
|
||||||
|
if ip < m.ip4[x] {
|
||||||
|
r = x
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nip := normalize4(ip, m.prefix4[x])
|
||||||
|
if nip == m.ip4[x] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
l = x + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func less6(a ipv6, b ipv6) bool {
|
||||||
|
return a.a < b.a || (a.a == b.a && a.b < b.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GeoIPMatcher) match6(ip ipv6) bool {
|
||||||
|
if len(m.ip6) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if less6(ip, m.ip6[0]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
size := uint32(len(m.ip6))
|
||||||
|
l := uint32(0)
|
||||||
|
r := size
|
||||||
|
for l < r {
|
||||||
|
x := (l + r) / 2
|
||||||
|
if less6(ip, m.ip6[x]) {
|
||||||
|
r = x
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if normalize6(ip, m.prefix6[x]) == m.ip6[x] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
l = x + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if the given ip is included by the GeoIP.
|
||||||
|
func (m *GeoIPMatcher) Match(ip net.IP) bool {
|
||||||
|
switch len(ip) {
|
||||||
|
case 4:
|
||||||
|
if m.reverseMatch {
|
||||||
|
return !m.match4(binary.BigEndian.Uint32(ip))
|
||||||
|
}
|
||||||
|
return m.match4(binary.BigEndian.Uint32(ip))
|
||||||
|
case 16:
|
||||||
|
if m.reverseMatch {
|
||||||
|
return !m.match6(ipv6{
|
||||||
|
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||||
|
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return m.match6(ipv6{
|
||||||
|
a: binary.BigEndian.Uint64(ip[0:8]),
|
||||||
|
b: binary.BigEndian.Uint64(ip[8:16]),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
|
||||||
|
type GeoIPMatcherContainer struct {
|
||||||
|
matchers []*GeoIPMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a new GeoIP set into the container.
|
||||||
|
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
|
||||||
|
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||||
|
if len(geoip.CountryCode) > 0 {
|
||||||
|
for _, m := range c.matchers {
|
||||||
|
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &GeoIPMatcher{
|
||||||
|
countryCode: geoip.CountryCode,
|
||||||
|
reverseMatch: geoip.ReverseMatch,
|
||||||
|
}
|
||||||
|
if err := m.Init(geoip.Cidr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(geoip.CountryCode) > 0 {
|
||||||
|
c.matchers = append(c.matchers, m)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalGeoIPContainer GeoIPMatcherContainer
|
||||||
|
|
||||||
type MultiGeoIPMatcher struct {
|
type MultiGeoIPMatcher struct {
|
||||||
matchers []*GeoIPMatcher
|
matchers []*GeoIPMatcher
|
||||||
}
|
}
|
||||||
|
@ -1,243 +0,0 @@
|
|||||||
package router
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CIDRList is an alias of []*CIDR to provide sort.Interface.
|
|
||||||
type CIDRList []*CIDR
|
|
||||||
|
|
||||||
// Len implements sort.Interface.
|
|
||||||
func (l *CIDRList) Len() int {
|
|
||||||
return len(*l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less implements sort.Interface.
|
|
||||||
func (l *CIDRList) Less(i int, j int) bool {
|
|
||||||
ci := (*l)[i]
|
|
||||||
cj := (*l)[j]
|
|
||||||
|
|
||||||
if len(ci.Ip) < len(cj.Ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ci.Ip) > len(cj.Ip) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := 0; k < len(ci.Ip); k++ {
|
|
||||||
if ci.Ip[k] < cj.Ip[k] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ci.Ip[k] > cj.Ip[k] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ci.Prefix < cj.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap implements sort.Interface.
|
|
||||||
func (l *CIDRList) Swap(i int, j int) {
|
|
||||||
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipv6 struct {
|
|
||||||
a uint64
|
|
||||||
b uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type GeoIPMatcher struct {
|
|
||||||
countryCode string
|
|
||||||
reverseMatch bool
|
|
||||||
ip4 []uint32
|
|
||||||
prefix4 []uint8
|
|
||||||
ip6 []ipv6
|
|
||||||
prefix6 []uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize4(ip uint32, prefix uint8) uint32 {
|
|
||||||
return (ip >> (32 - prefix)) << (32 - prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize6(ip ipv6, prefix uint8) ipv6 {
|
|
||||||
if prefix <= 64 {
|
|
||||||
ip.a = (ip.a >> (64 - prefix)) << (64 - prefix)
|
|
||||||
ip.b = 0
|
|
||||||
} else {
|
|
||||||
ip.b = (ip.b >> (128 - prefix)) << (128 - prefix)
|
|
||||||
}
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
|
|
||||||
ip4Count := 0
|
|
||||||
ip6Count := 0
|
|
||||||
|
|
||||||
for _, cidr := range cidrs {
|
|
||||||
ip := cidr.Ip
|
|
||||||
switch len(ip) {
|
|
||||||
case 4:
|
|
||||||
ip4Count++
|
|
||||||
case 16:
|
|
||||||
ip6Count++
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpect ip length: %d", len(ip))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cidrList := CIDRList(cidrs)
|
|
||||||
sort.Sort(&cidrList)
|
|
||||||
|
|
||||||
m.ip4 = make([]uint32, 0, ip4Count)
|
|
||||||
m.prefix4 = make([]uint8, 0, ip4Count)
|
|
||||||
m.ip6 = make([]ipv6, 0, ip6Count)
|
|
||||||
m.prefix6 = make([]uint8, 0, ip6Count)
|
|
||||||
|
|
||||||
for _, cidr := range cidrs {
|
|
||||||
ip := cidr.Ip
|
|
||||||
prefix := uint8(cidr.Prefix)
|
|
||||||
switch len(ip) {
|
|
||||||
case 4:
|
|
||||||
m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix))
|
|
||||||
m.prefix4 = append(m.prefix4, prefix)
|
|
||||||
case 16:
|
|
||||||
ip6 := ipv6{
|
|
||||||
a: binary.BigEndian.Uint64(ip[0:8]),
|
|
||||||
b: binary.BigEndian.Uint64(ip[8:16]),
|
|
||||||
}
|
|
||||||
ip6 = normalize6(ip6, prefix)
|
|
||||||
|
|
||||||
m.ip6 = append(m.ip6, ip6)
|
|
||||||
m.prefix6 = append(m.prefix6, prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
|
|
||||||
m.reverseMatch = isReverseMatch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GeoIPMatcher) match4(ip uint32) bool {
|
|
||||||
if len(m.ip4) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip < m.ip4[0] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
size := uint32(len(m.ip4))
|
|
||||||
l := uint32(0)
|
|
||||||
r := size
|
|
||||||
for l < r {
|
|
||||||
x := ((l + r) >> 1)
|
|
||||||
if ip < m.ip4[x] {
|
|
||||||
r = x
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nip := normalize4(ip, m.prefix4[x])
|
|
||||||
if nip == m.ip4[x] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
l = x + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func less6(a ipv6, b ipv6) bool {
|
|
||||||
return a.a < b.a || (a.a == b.a && a.b < b.b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *GeoIPMatcher) match6(ip ipv6) bool {
|
|
||||||
if len(m.ip6) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if less6(ip, m.ip6[0]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
size := uint32(len(m.ip6))
|
|
||||||
l := uint32(0)
|
|
||||||
r := size
|
|
||||||
for l < r {
|
|
||||||
x := (l + r) / 2
|
|
||||||
if less6(ip, m.ip6[x]) {
|
|
||||||
r = x
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if normalize6(ip, m.prefix6[x]) == m.ip6[x] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
l = x + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match returns true if the given ip is included by the GeoIP.
|
|
||||||
func (m *GeoIPMatcher) Match(ip net.IP) bool {
|
|
||||||
switch len(ip) {
|
|
||||||
case 4:
|
|
||||||
if m.reverseMatch {
|
|
||||||
return !m.match4(binary.BigEndian.Uint32(ip))
|
|
||||||
}
|
|
||||||
return m.match4(binary.BigEndian.Uint32(ip))
|
|
||||||
case 16:
|
|
||||||
if m.reverseMatch {
|
|
||||||
return !m.match6(ipv6{
|
|
||||||
a: binary.BigEndian.Uint64(ip[0:8]),
|
|
||||||
b: binary.BigEndian.Uint64(ip[8:16]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return m.match6(ipv6{
|
|
||||||
a: binary.BigEndian.Uint64(ip[0:8]),
|
|
||||||
b: binary.BigEndian.Uint64(ip[8:16]),
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
|
|
||||||
type GeoIPMatcherContainer struct {
|
|
||||||
matchers []*GeoIPMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a new GeoIP set into the container.
|
|
||||||
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
|
|
||||||
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
|
||||||
if len(geoip.CountryCode) > 0 {
|
|
||||||
for _, m := range c.matchers {
|
|
||||||
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &GeoIPMatcher{
|
|
||||||
countryCode: geoip.CountryCode,
|
|
||||||
reverseMatch: geoip.ReverseMatch,
|
|
||||||
}
|
|
||||||
if err := m.Init(geoip.Cidr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(geoip.CountryCode) > 0 {
|
|
||||||
c.matchers = append(c.matchers, m)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var globalGeoIPContainer GeoIPMatcherContainer
|
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func TestPool_MaxSize(t *testing.T) {
|
|||||||
size := 5
|
size := 5
|
||||||
pool := New(g, WithSize(size))
|
pool := New(g, WithSize(size))
|
||||||
|
|
||||||
items := []any{}
|
var items []any
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
item, _ := pool.Get()
|
item, _ := pool.Get()
|
||||||
|
@ -3,6 +3,8 @@ package process
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -19,3 +21,49 @@ const (
|
|||||||
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
|
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
|
||||||
return findProcessName(network, srcIP, srcPort)
|
return findProcessName(network, srcIP, srcPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShouldFindProcess(metadata *C.Metadata) bool {
|
||||||
|
if metadata.Process != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ip := range localIPs {
|
||||||
|
if ip.Equal(metadata.SrcIP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendLocalIPs(ip ...net.IP) {
|
||||||
|
localIPs = append(ip, localIPs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalIPs() []net.IP {
|
||||||
|
ips := []net.IP{net.IPv4zero, net.IPv6zero}
|
||||||
|
|
||||||
|
netInterfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
ips = append(ips, net.IPv4(127, 0, 0, 1), net.IPv6loopback)
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(netInterfaces); i++ {
|
||||||
|
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||||
|
adds, _ := netInterfaces[i].Addrs()
|
||||||
|
|
||||||
|
for _, address := range adds {
|
||||||
|
if ipNet, ok := address.(*net.IPNet); ok {
|
||||||
|
ips = append(ips, ipNet.IP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
var localIPs []net.IP
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
localIPs = getLocalIPs()
|
||||||
|
}
|
||||||
|
@ -69,7 +69,7 @@ func findProcessName(network string, ip net.IP, port int) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ip.Equal(srcIP) {
|
if !ip.Equal(srcIP) && (network == TCP || !srcIP.IsUnspecified()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3
|
|||||||
return 0, 0, fmt.Errorf("invalid uid(%d) or inode(%d)", uid, inode)
|
return 0, 0, fmt.Errorf("invalid uid(%d) or inode(%d)", uid, inode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uid, inode, nil
|
return inode, uid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
|
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
|
||||||
|
@ -10,6 +10,7 @@ type Enhancer interface {
|
|||||||
FakeIPEnabled() bool
|
FakeIPEnabled() bool
|
||||||
MappingEnabled() bool
|
MappingEnabled() bool
|
||||||
IsFakeIP(net.IP) bool
|
IsFakeIP(net.IP) bool
|
||||||
|
IsFakeBroadcastIP(net.IP) bool
|
||||||
IsExistFakeIP(net.IP) bool
|
IsExistFakeIP(net.IP) bool
|
||||||
FindHostByIP(net.IP) (string, bool)
|
FindHostByIP(net.IP) (string, bool)
|
||||||
}
|
}
|
||||||
@ -38,6 +39,14 @@ func IsFakeIP(ip net.IP) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsFakeBroadcastIP(ip net.IP) bool {
|
||||||
|
if mapper := DefaultHostMapper; mapper != nil {
|
||||||
|
return mapper.IsFakeBroadcastIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func IsExistFakeIP(ip net.IP) bool {
|
func IsExistFakeIP(ip net.IP) bool {
|
||||||
if mapper := DefaultHostMapper; mapper != nil {
|
if mapper := DefaultHostMapper; mapper != nil {
|
||||||
return mapper.IsExistFakeIP(ip)
|
return mapper.IsExistFakeIP(ip)
|
||||||
|
157
config/config.go
157
config/config.go
@ -7,6 +7,7 @@ import (
|
|||||||
R "github.com/Dreamacro/clash/rule"
|
R "github.com/Dreamacro/clash/rule"
|
||||||
RP "github.com/Dreamacro/clash/rule/provider"
|
RP "github.com/Dreamacro/clash/rule/provider"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
@ -26,6 +28,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
@ -47,7 +50,7 @@ type General struct {
|
|||||||
AutoIptables bool `json:"auto-iptables"`
|
AutoIptables bool `json:"auto-iptables"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound
|
// Inbound config
|
||||||
type Inbound struct {
|
type Inbound struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
@ -59,7 +62,7 @@ type Inbound struct {
|
|||||||
BindAddress string `json:"bind-address"`
|
BindAddress string `json:"bind-address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controller
|
// Controller config
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
ExternalController string `json:"-"`
|
ExternalController string `json:"-"`
|
||||||
ExternalUI string `json:"-"`
|
ExternalUI string `json:"-"`
|
||||||
@ -104,10 +107,11 @@ type Profile struct {
|
|||||||
|
|
||||||
// Tun config
|
// Tun config
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Stack string `yaml:"stack" json:"stack"`
|
Device string `yaml:"device" json:"device"`
|
||||||
DnsHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
||||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
|
||||||
|
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Script config
|
// Script config
|
||||||
@ -157,6 +161,14 @@ type RawFallbackFilter struct {
|
|||||||
GeoSite []string `yaml:"geosite"`
|
GeoSite []string `yaml:"geosite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RawTun struct {
|
||||||
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
|
Device string `yaml:"device" json:"device"`
|
||||||
|
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
||||||
|
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
||||||
|
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||||
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
@ -179,17 +191,17 @@ type RawConfig struct {
|
|||||||
GeodataLoader string `yaml:"geodata-loader"`
|
GeodataLoader string `yaml:"geodata-loader"`
|
||||||
AutoIptables bool `yaml:"auto-iptables"`
|
AutoIptables bool `yaml:"auto-iptables"`
|
||||||
|
|
||||||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"`
|
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
DNS RawDNS `yaml:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Tun Tun `yaml:"tun"`
|
Tun RawTun `yaml:"tun"`
|
||||||
Experimental Experimental `yaml:"experimental"`
|
Experimental Experimental `yaml:"experimental"`
|
||||||
Profile Profile `yaml:"profile"`
|
Profile Profile `yaml:"profile"`
|
||||||
Proxy []map[string]any `yaml:"proxies"`
|
Proxy []map[string]any `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"rules"`
|
Rule []string `yaml:"rules"`
|
||||||
Script Script `yaml:"script"`
|
Script Script `yaml:"script"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse config
|
// Parse config
|
||||||
@ -218,16 +230,18 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
Rule: []string{},
|
Rule: []string{},
|
||||||
Proxy: []map[string]any{},
|
Proxy: []map[string]any{},
|
||||||
ProxyGroup: []map[string]any{},
|
ProxyGroup: []map[string]any{},
|
||||||
Tun: Tun{
|
Tun: RawTun{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Stack: "gvisor",
|
Device: "",
|
||||||
DnsHijack: []string{"198.18.0.2:53"},
|
Stack: C.TunGvisor,
|
||||||
|
DNSHijack: []string{"198.18.0.2:53"}, // default hijack all dns query
|
||||||
AutoRoute: true,
|
AutoRoute: true,
|
||||||
},
|
},
|
||||||
DNS: RawDNS{
|
DNS: RawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
UseHosts: true,
|
UseHosts: true,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
EnhancedMode: C.DNSMapping,
|
||||||
|
FakeIPRange: "198.18.0.1/16",
|
||||||
FallbackFilter: RawFallbackFilter{
|
FallbackFilter: RawFallbackFilter{
|
||||||
GeoIP: true,
|
GeoIP: true,
|
||||||
GeoIPCode: "CN",
|
GeoIPCode: "CN",
|
||||||
@ -243,6 +257,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
NameServer: []string{
|
NameServer: []string{
|
||||||
"223.5.5.5",
|
"223.5.5.5",
|
||||||
"119.29.29.29",
|
"119.29.29.29",
|
||||||
|
"https://doh.pub/dns-query",
|
||||||
|
"tls://223.5.5.5:853",
|
||||||
},
|
},
|
||||||
FakeIPFilter: []string{
|
FakeIPFilter: []string{
|
||||||
"dns.msftnsci.com",
|
"dns.msftnsci.com",
|
||||||
@ -279,7 +295,13 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.General = general
|
config.General = general
|
||||||
|
|
||||||
config.Tun = &rawCfg.Tun
|
tunCfg, err := parseTun(rawCfg.Tun, config.General)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Tun = tunCfg
|
||||||
|
|
||||||
|
dialer.DefaultInterface.Store(config.General.Interface)
|
||||||
|
|
||||||
proxies, providers, err := parseProxies(rawCfg)
|
proxies, providers, err := parseProxies(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -361,13 +383,14 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
|
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) {
|
||||||
proxies = make(map[string]C.Proxy)
|
proxies = make(map[string]C.Proxy)
|
||||||
providersMap = make(map[string]providerTypes.ProxyProvider)
|
providersMap = make(map[string]providerTypes.ProxyProvider)
|
||||||
var proxyList []string
|
|
||||||
_proxiesList := list.New()
|
|
||||||
_groupsList := list.New()
|
|
||||||
proxiesConfig := cfg.Proxy
|
proxiesConfig := cfg.Proxy
|
||||||
groupsConfig := cfg.ProxyGroup
|
groupsConfig := cfg.ProxyGroup
|
||||||
providersConfig := cfg.ProxyProvider
|
providersConfig := cfg.ProxyProvider
|
||||||
|
|
||||||
|
var proxyList []string
|
||||||
|
_proxiesList := list.New()
|
||||||
|
_groupsList := list.New()
|
||||||
|
|
||||||
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||||
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||||
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
|
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
|
||||||
@ -417,6 +440,13 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
providersMap[name] = pd
|
providersMap[name] = pd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, proxyProvider := range providersMap {
|
||||||
|
log.Infoln("Start initial provider %s", proxyProvider.Name())
|
||||||
|
if err := proxyProvider.Initial(); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", proxyProvider.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parse proxy group
|
// parse proxy group
|
||||||
for idx, mapping := range groupsConfig {
|
for idx, mapping := range groupsConfig {
|
||||||
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
||||||
@ -444,7 +474,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := []C.Proxy{}
|
var ps []C.Proxy
|
||||||
for _, v := range proxyList {
|
for _, v := range proxyList {
|
||||||
ps = append(ps, proxies[v])
|
ps = append(ps, proxies[v])
|
||||||
}
|
}
|
||||||
@ -543,29 +573,27 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l := len(rule)
|
||||||
|
|
||||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
|
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
|
||||||
payload = strings.Join(rule[1:len(rule)-1], ",")
|
target = rule[l-1]
|
||||||
target = rule[len(rule)-1]
|
payload = strings.Join(rule[1:l-1], ",")
|
||||||
} else {
|
} else {
|
||||||
switch l := len(rule); {
|
if l < 2 {
|
||||||
case l == 2:
|
|
||||||
target = rule[1]
|
|
||||||
case l == 3:
|
|
||||||
if ruleName == "MATCH" {
|
|
||||||
payload = ""
|
|
||||||
target = rule[1]
|
|
||||||
params = rule[2:]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
payload = rule[1]
|
|
||||||
target = rule[2]
|
|
||||||
case l >= 4:
|
|
||||||
payload = rule[1]
|
|
||||||
target = rule[2]
|
|
||||||
params = rule[3:]
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
if l < 4 {
|
||||||
|
rule = append(rule, make([]string, 4-l)...)
|
||||||
|
}
|
||||||
|
if ruleName == "MATCH" {
|
||||||
|
l = 2
|
||||||
|
}
|
||||||
|
if l >= 3 {
|
||||||
|
l = 3
|
||||||
|
payload = rule[1]
|
||||||
|
}
|
||||||
|
target = rule[l-1]
|
||||||
|
params = rule[l:]
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := proxies[target]; mode != T.Script && !ok {
|
if _, ok := proxies[target]; mode != T.Script && !ok {
|
||||||
@ -681,6 +709,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
Net: dnsNetType,
|
Net: dnsNetType,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
ProxyAdapter: u.Fragment,
|
ProxyAdapter: u.Fragment,
|
||||||
|
Interface: dialer.DefaultInterface.Load(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -879,3 +908,37 @@ func cleanPyKeywords(code string) string {
|
|||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
||||||
|
if (rawTun.Enable || general.TProxyPort != 0) && general.Interface == "" {
|
||||||
|
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
|
||||||
|
if err != nil || autoDetectInterfaceName == "" {
|
||||||
|
return nil, fmt.Errorf("can not find auto detect interface: %w. you must be detect `interface-name` if tun set to enable or `tproxy-port` isn't zore", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
general.Interface = autoDetectInterfaceName
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsHijack []netip.AddrPort
|
||||||
|
|
||||||
|
for _, d := range rawTun.DNSHijack {
|
||||||
|
if _, after, ok := strings.Cut(d, "://"); ok {
|
||||||
|
d = after
|
||||||
|
}
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse dns-hijack url error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsHijack = append(dnsHijack, addrPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Tun{
|
||||||
|
Enable: rawTun.Enable,
|
||||||
|
Device: rawTun.Device,
|
||||||
|
Stack: rawTun.Stack,
|
||||||
|
DNSHijack: dnsHijack,
|
||||||
|
AutoRoute: rawTun.AutoRoute,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -67,7 +67,7 @@ func downloadGeoSite(path string) (err error) {
|
|||||||
|
|
||||||
func initGeoSite() error {
|
func initGeoSite() error {
|
||||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||||
log.Infoln("Need GeoSite but can't find GeoSite.dat, start download")
|
log.Infoln("Can't find GeoSite.dat, start download")
|
||||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ func initGeoSite() error {
|
|||||||
func initGeoIP() error {
|
func initGeoIP() error {
|
||||||
if C.GeodataMode {
|
if C.GeodataMode {
|
||||||
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
||||||
log.Infoln("Need GeoIP but can't find GeoIP.dat, start download")
|
log.Infoln("Can't find GeoIP.dat, start download")
|
||||||
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
|
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
|
||||||
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
|
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -73,16 +73,16 @@ func (t Type) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
// Metadata is used to store connection address
|
// Metadata is used to store connection address
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
NetWork NetWork `json:"network"`
|
NetWork NetWork `json:"network"`
|
||||||
Type Type `json:"type"`
|
Type Type `json:"type"`
|
||||||
SrcIP net.IP `json:"sourceIP"`
|
SrcIP net.IP `json:"sourceIP"`
|
||||||
DstIP net.IP `json:"destinationIP"`
|
DstIP net.IP `json:"destinationIP"`
|
||||||
SrcPort string `json:"sourcePort"`
|
SrcPort string `json:"sourcePort"`
|
||||||
DstPort string `json:"destinationPort"`
|
DstPort string `json:"destinationPort"`
|
||||||
AddrType int `json:"-"`
|
AddrType int `json:"-"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Process string `json:"process"`
|
DNSMode DNSMode `json:"dnsMode"`
|
||||||
DNSMode DNSMode `json:"dnsMode"`
|
Process string `json:"process"`
|
||||||
ProcessPath string `json:"processPath"`
|
ProcessPath string `json:"processPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,4 +75,5 @@ type Rule interface {
|
|||||||
ShouldResolveIP() bool
|
ShouldResolveIP() bool
|
||||||
ShouldFindProcess() bool
|
ShouldFindProcess() bool
|
||||||
RuleExtra() *RuleExtra
|
RuleExtra() *RuleExtra
|
||||||
|
SetRuleExtra(re *RuleExtra)
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TunBroadcastAddr = net.IPv4(198, 18, 255, 255)
|
|
||||||
|
|
||||||
type RuleExtra struct {
|
type RuleExtra struct {
|
||||||
Network NetWork
|
Network NetWork
|
||||||
SourceIPs []*net.IPNet
|
SourceIPs []*net.IPNet
|
||||||
|
66
constant/tun.go
Normal file
66
constant/tun.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var StackTypeMapping = map[string]TUNStack{
|
||||||
|
strings.ToUpper(TunGvisor.String()): TunGvisor,
|
||||||
|
strings.ToUpper(TunSystem.String()): TunSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TunGvisor TUNStack = iota
|
||||||
|
TunSystem
|
||||||
|
)
|
||||||
|
|
||||||
|
type TUNStack int
|
||||||
|
|
||||||
|
// UnmarshalYAML unserialize TUNStack with yaml
|
||||||
|
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var tp string
|
||||||
|
if err := unmarshal(&tp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mode, exist := StackTypeMapping[strings.ToUpper(tp)]
|
||||||
|
if !exist {
|
||||||
|
return errors.New("invalid tun stack")
|
||||||
|
}
|
||||||
|
*e = mode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML serialize TUNStack with yaml
|
||||||
|
func (e TUNStack) MarshalYAML() (any, error) {
|
||||||
|
return e.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unserialize TUNStack with json
|
||||||
|
func (e *TUNStack) UnmarshalJSON(data []byte) error {
|
||||||
|
var tp string
|
||||||
|
json.Unmarshal(data, &tp)
|
||||||
|
mode, exist := StackTypeMapping[strings.ToUpper(tp)]
|
||||||
|
if !exist {
|
||||||
|
return errors.New("invalid tun stack")
|
||||||
|
}
|
||||||
|
*e = mode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON serialize TUNStack with json
|
||||||
|
func (e TUNStack) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(e.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TUNStack) String() string {
|
||||||
|
switch e {
|
||||||
|
case TunGvisor:
|
||||||
|
return "gVisor"
|
||||||
|
case TunSystem:
|
||||||
|
return "System"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,6 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
ip net.IP
|
ip net.IP
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if ip = net.ParseIP(c.host); ip == nil {
|
if ip = net.ParseIP(c.host); ip == nil {
|
||||||
if c.r == nil {
|
if c.r == nil {
|
||||||
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||||
|
@ -40,7 +40,19 @@ func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pool := h.fakePool; pool != nil {
|
if pool := h.fakePool; pool != nil {
|
||||||
return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(ip)
|
return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(ip) && !pool.Broadcast().Equal(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResolverEnhancer) IsFakeBroadcastIP(ip net.IP) bool {
|
||||||
|
if !h.FakeIPEnabled() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pool := h.fakePool; pool != nil {
|
||||||
|
return pool.Broadcast().Equal(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -150,7 +150,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (*D.Msg, error) {
|
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
r := client
|
r := client
|
||||||
@ -174,8 +174,8 @@ func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := elm.(*D.Msg)
|
msg = elm.(*D.Msg)
|
||||||
return msg, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||||
@ -216,7 +216,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||||
res := <-r.asyncExchange(ctx, matched, m)
|
res := <-r.asyncExchange(ctx, matched, m)
|
||||||
return res.Msg, res.Error
|
return res.Msg, res.Error
|
||||||
@ -233,20 +233,23 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
|||||||
|
|
||||||
if r.fallback == nil || len(r.fallback) == 0 { // directly return if no fallback servers are available
|
if r.fallback == nil || len(r.fallback) == 0 { // directly return if no fallback servers are available
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
return res.Msg, res.Error
|
msg, err = res.Msg, res.Error
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||||
if !r.shouldIPFallback(ips[0]) {
|
if !r.shouldIPFallback(ips[0]) {
|
||||||
return res.Msg, res.Error // no need to wait for fallback result
|
msg, err = res.Msg, res.Error // no need to wait for fallback result
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res = <-r.asyncExchange(ctx, r.fallback, m)
|
res = <-r.asyncExchange(ctx, r.fallback, m)
|
||||||
return res.Msg, res.Error
|
msg, err = res.Msg, res.Error
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
|
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
|
||||||
|
14
go.mod
14
go.mod
@ -11,12 +11,11 @@ require (
|
|||||||
github.com/gofrs/uuid v4.2.0+incompatible
|
github.com/gofrs/uuid v4.2.0+incompatible
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd
|
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd
|
||||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99
|
|
||||||
github.com/lucas-clemente/quic-go v0.25.0
|
github.com/lucas-clemente/quic-go v0.25.0
|
||||||
github.com/miekg/dns v1.1.47
|
github.com/miekg/dns v1.1.47
|
||||||
github.com/oschwald/geoip2-golang v1.6.1
|
github.com/oschwald/geoip2-golang v1.6.1
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
@ -25,10 +24,12 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
|
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20220310012736-ae6bc4dd64e1
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.27.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gvisor.dev/gvisor v0.0.0-20220129032118-ed00636ef990
|
gvisor.dev/gvisor v0.0.0-20220315202956-f1399ecf1672
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -47,11 +48,10 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
golang.zx2c4.com/go118/netip v0.0.0-20211106132939-9d41d90554dd // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
)
|
)
|
||||||
|
25
go.sum
25
go.sum
@ -104,8 +104,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99 h1:dkEFEnGUg2z/FAPywWr4yfR/sWDQK76qn3J4Y5H2hJs=
|
|
||||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o=
|
|
||||||
github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc=
|
github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc=
|
||||||
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
|
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
@ -196,8 +194,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
|
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
|
||||||
@ -323,8 +322,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
|
||||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
|
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
|
||||||
@ -352,12 +351,12 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/go118/netip v0.0.0-20211106132939-9d41d90554dd h1:gUHae7sCd+tFJLcCximWeBFD2b6Jg3O7UaNaPvjIJHc=
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||||
golang.zx2c4.com/go118/netip v0.0.0-20211106132939-9d41d90554dd/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1 h1:OnYw96PF+CsIMrqWo5QP3Q59q5hY1rFErk/yN3cS+JQ=
|
golang.zx2c4.com/wireguard v0.0.0-20220310012736-ae6bc4dd64e1 h1:iuQdvJn3LrXxz3Iony1qBGVS7kEy2uHYnnjHsVbzq/s=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1/go.mod h1:EApyTk/ZNrkbZjurHL1nleDYnsPpJYBO7LZEBCyDAHk=
|
golang.zx2c4.com/wireguard v0.0.0-20220310012736-ae6bc4dd64e1/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.2 h1:C3kUahG6QPhXUYSMesW0t1mDo1XCwls2c8Bz16umS44=
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.2/go.mod h1:KptYG5fXTsP1sHafw0BnWHxobtR6QZHWY6SVWq5R0PM=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
@ -400,8 +399,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
gvisor.dev/gvisor v0.0.0-20220129032118-ed00636ef990 h1:fTgWAYpliP19U3FX8+tI2TZGXnnk45g18frOuZxKay4=
|
gvisor.dev/gvisor v0.0.0-20220315202956-f1399ecf1672 h1:aXIFpjZYl3zv2rQyr4rSit5Uq0k7BVXC8lJaDa4Cg7M=
|
||||||
gvisor.dev/gvisor v0.0.0-20220129032118-ed00636ef990/go.mod h1:vmN0Pug/s8TJmpnt30DvrEfZ5vDl52psGLU04tFuK2U=
|
gvisor.dev/gvisor v0.0.0-20220315202956-f1399ecf1672/go.mod h1:V4WNP2Uwtx69eOhvLDSQ734EaTJTaBI3P8KgRAlROsg=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tproxy"
|
"github.com/Dreamacro/clash/listener/tproxy"
|
||||||
@ -28,7 +27,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
P "github.com/Dreamacro/clash/listener"
|
P "github.com/Dreamacro/clash/listener"
|
||||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
"github.com/Dreamacro/clash/listener/tun/dev"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
@ -76,18 +74,21 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
mux.Lock()
|
mux.Lock()
|
||||||
defer mux.Unlock()
|
defer mux.Unlock()
|
||||||
|
|
||||||
|
log.SetLevel(log.DEBUG)
|
||||||
|
|
||||||
updateUsers(cfg.Users)
|
updateUsers(cfg.Users)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateGeneral(cfg.General, cfg.Tun, force)
|
updateGeneral(cfg.General, cfg.Tun, force)
|
||||||
updateProxies(cfg.Proxies, cfg.Providers)
|
updateProxies(cfg.Proxies, cfg.Providers)
|
||||||
updateRules(cfg.Rules, cfg.RuleProviders)
|
updateRules(cfg.Rules, cfg.RuleProviders)
|
||||||
updateIPTables(cfg.DNS, cfg.General, cfg.Tun)
|
updateIPTables(cfg.DNS, cfg.General.TProxyPort, cfg.General.Interface, cfg.Tun.Enable)
|
||||||
updateDNS(cfg.DNS, cfg.Tun)
|
updateDNS(cfg.DNS, cfg.Tun)
|
||||||
updateTun(cfg.Tun)
|
updateTun(cfg.Tun, cfg.DNS.FakeIPRange.IPNet().String())
|
||||||
updateExperimental(cfg)
|
updateExperimental(cfg)
|
||||||
loadProvider(cfg.RuleProviders, cfg.Providers)
|
loadProvider(cfg.RuleProviders, cfg.Providers)
|
||||||
updateProfile(cfg)
|
updateProfile(cfg)
|
||||||
|
|
||||||
|
log.SetLevel(cfg.General.LogLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneral() *config.General {
|
func GetGeneral() *config.General {
|
||||||
@ -119,15 +120,7 @@ func GetGeneral() *config.General {
|
|||||||
|
|
||||||
func updateExperimental(c *config.Config) {}
|
func updateExperimental(c *config.Config) {}
|
||||||
|
|
||||||
func updateDNS(c *config.DNS, Tun *config.Tun) {
|
func updateDNS(c *config.DNS, t *config.Tun) {
|
||||||
if !c.Enable && !Tun.Enable {
|
|
||||||
resolver.DefaultResolver = nil
|
|
||||||
resolver.MainResolver = nil
|
|
||||||
resolver.DefaultHostMapper = nil
|
|
||||||
dns.ReCreateServer("", nil, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := dns.Config{
|
cfg := dns.Config{
|
||||||
Main: c.NameServer,
|
Main: c.NameServer,
|
||||||
Fallback: c.Fallback,
|
Fallback: c.Fallback,
|
||||||
@ -158,14 +151,21 @@ func updateDNS(c *config.DNS, Tun *config.Tun) {
|
|||||||
resolver.DefaultResolver = r
|
resolver.DefaultResolver = r
|
||||||
resolver.MainResolver = mr
|
resolver.MainResolver = mr
|
||||||
resolver.DefaultHostMapper = m
|
resolver.DefaultHostMapper = m
|
||||||
if Tun.Enable && !strings.EqualFold(Tun.Stack, "gVisor") {
|
|
||||||
|
if t.Enable {
|
||||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
||||||
} else {
|
|
||||||
resolver.DefaultLocalServer = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Enable {
|
if c.Enable {
|
||||||
dns.ReCreateServer(c.Listen, r, m)
|
dns.ReCreateServer(c.Listen, r, m)
|
||||||
|
} else {
|
||||||
|
if !t.Enable {
|
||||||
|
resolver.DefaultResolver = nil
|
||||||
|
resolver.MainResolver = nil
|
||||||
|
resolver.DefaultHostMapper = nil
|
||||||
|
resolver.DefaultLocalServer = nil
|
||||||
|
}
|
||||||
|
dns.ReCreateServer("", nil, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,33 +213,30 @@ func loadProvider(ruleProviders map[string]*provider.RuleProvider, proxyProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateTun(tun *config.Tun, tunAddressPrefix string) {
|
||||||
|
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
|
||||||
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
|
func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
|
||||||
tunnel.SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
resolver.DisableIPv6 = !general.IPv6
|
resolver.DisableIPv6 = !general.IPv6
|
||||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||||
|
|
||||||
if (Tun.Enable || general.TProxyPort != 0) && general.Interface == "" {
|
dialer.DefaultInterface.Store(general.Interface)
|
||||||
autoDetectInterfaceName, err := dev.GetAutoDetectInterface()
|
if dialer.DefaultInterface.Load() != "" {
|
||||||
if err == nil {
|
log.Infoln("Use interface name: %s", general.Interface)
|
||||||
if autoDetectInterfaceName != "" && autoDetectInterfaceName != "<nil>" {
|
|
||||||
general.Interface = autoDetectInterfaceName
|
|
||||||
} else {
|
|
||||||
log.Debugln("Auto detect interface name is empty.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debugln("Can not find auto detect interface. %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer.DefaultInterface.Store(general.Interface)
|
if general.RoutingMark > 0 || (general.RoutingMark == 0 && general.TProxyPort == 0) {
|
||||||
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
||||||
|
if general.RoutingMark > 0 {
|
||||||
log.Infoln("Use interface name: %s", general.Interface)
|
log.Infoln("Use routing mark: %#x", general.RoutingMark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iface.FlushCache()
|
iface.FlushCache()
|
||||||
|
|
||||||
if !force {
|
if !force {
|
||||||
log.SetLevel(general.LogLevel)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,18 +257,6 @@ func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
|
|||||||
P.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
P.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
||||||
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
||||||
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||||
|
|
||||||
log.SetLevel(general.LogLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateTun(Tun *config.Tun) {
|
|
||||||
if Tun == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tcpIn := tunnel.TCPIn()
|
|
||||||
udpIn := tunnel.UDPIn()
|
|
||||||
|
|
||||||
P.ReCreateTun(*Tun, tcpIn, udpIn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUsers(users []auth.AuthUser) {
|
func updateUsers(users []auth.AuthUser) {
|
||||||
@ -317,8 +302,28 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIPTables(dns *config.DNS, general *config.General, tun *config.Tun) {
|
func updateIPTables(dns *config.DNS, tProxyPort int, interfaceName string, tunEnable bool) {
|
||||||
if runtime.GOOS != "linux" || dns.Listen == "" || general.TProxyPort == 0 || tun.Enable || !general.AutoIptables {
|
tproxy.CleanUpTProxyLinuxIPTables()
|
||||||
|
|
||||||
|
if runtime.GOOS != "linux" || tProxyPort == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Setting iptables failed: %s", err.Error())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !dns.Enable || dns.Listen == "" {
|
||||||
|
err = fmt.Errorf("DNS server must be enable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tunEnable {
|
||||||
|
err = fmt.Errorf("TUN device must be disabe")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,19 +337,15 @@ func updateIPTables(dns *config.DNS, general *config.General, tun *config.Tun) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tproxy.CleanUpTProxyLinuxIPTables()
|
if dialer.DefaultRoutingMark.Load() == 0 {
|
||||||
dialer.DefaultRoutingMark.Store(2158)
|
dialer.DefaultRoutingMark.Store(2158)
|
||||||
|
|
||||||
err = tproxy.SetTProxyLinuxIPTables(general.Interface, general.TProxyPort, dnsPort)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Can not setting iptables for TProxy on linux, %s", err.Error())
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = tproxy.SetTProxyLinuxIPTables(interfaceName, tProxyPort, dnsPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CleanUp() {
|
func Cleanup() {
|
||||||
P.CleanUp()
|
P.Cleanup()
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
tproxy.CleanUpTProxyLinuxIPTables()
|
tproxy.CleanUpTProxyLinuxIPTables()
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,6 @@ func Parse(options ...Option) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CleanUp() {
|
func Cleanup() {
|
||||||
executor.CleanUp()
|
executor.Cleanup()
|
||||||
}
|
}
|
||||||
|
@ -78,10 +78,6 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
|
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
|
||||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
|
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
|
||||||
|
|
||||||
if general.Tun != nil {
|
|
||||||
P.ReCreateTun(*general.Tun, tcpIn, udpIn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if general.Mode != nil {
|
if general.Mode != nil {
|
||||||
tunnel.SetMode(*general.Mode)
|
tunnel.SetMode(*general.Mode)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/listener/inner"
|
"github.com/Dreamacro/clash/listener/inner"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ var (
|
|||||||
tproxyUDPListener *tproxy.UDPListener
|
tproxyUDPListener *tproxy.UDPListener
|
||||||
mixedListener *mixed.Listener
|
mixedListener *mixed.Listener
|
||||||
mixedUDPLister *socks.UDPListener
|
mixedUDPLister *socks.UDPListener
|
||||||
tunAdapter ipstack.TunAdapter
|
tunStackListener ipstack.Stack
|
||||||
|
|
||||||
// lock for recreate function
|
// lock for recreate function
|
||||||
socksMux sync.Mutex
|
socksMux sync.Mutex
|
||||||
@ -66,18 +65,6 @@ func SetAllowLan(al bool) {
|
|||||||
allowLan = al
|
allowLan = al
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tun() config.Tun {
|
|
||||||
if tunAdapter == nil {
|
|
||||||
return config.Tun{}
|
|
||||||
}
|
|
||||||
return config.Tun{
|
|
||||||
Enable: true,
|
|
||||||
Stack: tunAdapter.Stack(),
|
|
||||||
DnsHijack: tunAdapter.DnsHijack(),
|
|
||||||
AutoRoute: tunAdapter.AutoRoute(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBindAddress(host string) {
|
func SetBindAddress(host string) {
|
||||||
bindAddress = host
|
bindAddress = host
|
||||||
}
|
}
|
||||||
@ -323,47 +310,28 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||||||
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||||
}
|
}
|
||||||
|
|
||||||
//func ReCreateTun(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
func ReCreateTun(tunConf *config.Tun, tunAddressPrefix string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||||
// tunMux.Lock()
|
|
||||||
// defer tunMux.Unlock()
|
|
||||||
//
|
|
||||||
// if tunAdapter != nil {
|
|
||||||
// tunAdapter.Close()
|
|
||||||
// tunAdapter = nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if !conf.Enable {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var err error
|
|
||||||
// tunAdapter, err = tun.New(conf, tcpIn, udpIn)
|
|
||||||
//
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
|
|
||||||
func ReCreateTun(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
|
||||||
tunMux.Lock()
|
tunMux.Lock()
|
||||||
defer tunMux.Unlock()
|
defer tunMux.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("Start TUN interface error: %s", err.Error())
|
log.Errorln("Start TUN listening error: %s", err.Error())
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if tunAdapter != nil {
|
if tunStackListener != nil {
|
||||||
tunAdapter.Close()
|
tunStackListener.Close()
|
||||||
tunAdapter = nil
|
tunStackListener = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !conf.Enable {
|
if !tunConf.Enable {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tunAdapter, err = tun.New(conf, tcpIn, udpIn)
|
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Failed to start TUN interface: %s", err.Error())
|
log.Warnln("Failed to start TUN interface: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -425,11 +393,8 @@ func genAddr(host string, port int, allowLan bool) string {
|
|||||||
return fmt.Sprintf("127.0.0.1:%d", port)
|
return fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp clean up something
|
func Cleanup() {
|
||||||
func CleanUp() {
|
if tunStackListener != nil {
|
||||||
if runtime.GOOS == "windows" {
|
_ = tunStackListener.Close()
|
||||||
if tunAdapter != nil {
|
|
||||||
tunAdapter.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,17 @@ package tproxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cmd"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
interfaceName = ""
|
|
||||||
tproxyPort = 0
|
|
||||||
dnsPort = 0
|
dnsPort = 0
|
||||||
|
tProxyPort = 0
|
||||||
|
interfaceName = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -24,7 +23,7 @@ const (
|
|||||||
|
|
||||||
func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
||||||
var err error
|
var err error
|
||||||
if _, err = execCmd("iptables -V"); err != nil {
|
if err = execCmd("iptables -V"); err != nil {
|
||||||
return fmt.Errorf("current operations system [%s] are not support iptables or command iptables does not exist", runtime.GOOS)
|
return fmt.Errorf("current operations system [%s] are not support iptables or command iptables does not exist", runtime.GOOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interfaceName = ifname
|
interfaceName = ifname
|
||||||
tproxyPort = tport
|
tProxyPort = tport
|
||||||
dnsPort = dport
|
dnsPort = dport
|
||||||
|
|
||||||
// add route
|
// add route
|
||||||
@ -63,8 +62,8 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
|||||||
addLocalnetworkToChain("clash_prerouting")
|
addLocalnetworkToChain("clash_prerouting")
|
||||||
execCmd("iptables -t mangle -A clash_prerouting -p tcp -m socket -j clash_divert")
|
execCmd("iptables -t mangle -A clash_prerouting -p tcp -m socket -j clash_divert")
|
||||||
execCmd("iptables -t mangle -A clash_prerouting -p udp -m socket -j clash_divert")
|
execCmd("iptables -t mangle -A clash_prerouting -p udp -m socket -j clash_divert")
|
||||||
execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p tcp -j TPROXY --on-port %d --tproxy-mark %s/%s", tproxyPort, PROXY_FWMARK, PROXY_FWMARK))
|
execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p tcp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK))
|
||||||
execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tproxyPort, PROXY_FWMARK, PROXY_FWMARK))
|
execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK))
|
||||||
execCmd("iptables -t mangle -A PREROUTING -j clash_prerouting")
|
execCmd("iptables -t mangle -A PREROUTING -j clash_prerouting")
|
||||||
|
|
||||||
execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort))
|
execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort))
|
||||||
@ -96,20 +95,22 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
|||||||
execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j clash_dns_output")
|
execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j clash_dns_output")
|
||||||
execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j clash_dns_output")
|
execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j clash_dns_output")
|
||||||
|
|
||||||
log.Infoln("[TProxy] Setting iptables completed")
|
log.Infoln("[TPROXY] Setting iptables completed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CleanUpTProxyLinuxIPTables() {
|
func CleanUpTProxyLinuxIPTables() {
|
||||||
if interfaceName == "" || tproxyPort == 0 || dnsPort == 0 {
|
if interfaceName == "" || tProxyPort == 0 || dnsPort == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warnln("Clean up tproxy linux iptables")
|
log.Warnln("Clean up tproxy linux iptables")
|
||||||
|
|
||||||
dialer.DefaultRoutingMark.Store(0)
|
if int(dialer.DefaultRoutingMark.Load()) == 2158 {
|
||||||
|
dialer.DefaultRoutingMark.Store(0)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := execCmd("iptables -t mangle -L clash_divert"); err != nil {
|
if err := execCmd("iptables -t mangle -L clash_divert"); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,16 +166,13 @@ func addLocalnetworkToChain(chain string) {
|
|||||||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 255.255.255.255/32 -j RETURN", chain))
|
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 255.255.255.255/32 -j RETURN", chain))
|
||||||
}
|
}
|
||||||
|
|
||||||
func execCmd(cmdstr string) (string, error) {
|
func execCmd(cmdStr string) error {
|
||||||
log.Debugln("[TProxy] %s", cmdstr)
|
log.Debugln("[TPROXY] %s", cmdStr)
|
||||||
|
|
||||||
args := strings.Split(cmdstr, " ")
|
_, err := cmd.ExecCmd(cmdStr)
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[TProxy] error: %s, %s", err.Error(), string(out))
|
log.Warnln("[TPROXY] exec cmd: %v", err)
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(out), nil
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package dev
|
|
||||||
|
|
||||||
// TunDevice is cross-platform tun interface
|
|
||||||
type TunDevice interface {
|
|
||||||
Name() string
|
|
||||||
URL() string
|
|
||||||
MTU() (int, error)
|
|
||||||
IsClose() bool
|
|
||||||
Close() error
|
|
||||||
Read(buff []byte) (int, error)
|
|
||||||
Write(buff []byte) (int, error)
|
|
||||||
}
|
|
@ -1,541 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
// +build darwin
|
|
||||||
|
|
||||||
package dev
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
utunControlName = "com.apple.net.utun_control"
|
|
||||||
iocOut = 0x40000000
|
|
||||||
iocIn = 0x80000000
|
|
||||||
iocInout = iocIn | iocOut
|
|
||||||
)
|
|
||||||
|
|
||||||
// _CTLIOCGINFO value derived from /usr/include/sys/{kern_control,ioccom}.h
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/master/bsd/sys/ioccom.h
|
|
||||||
|
|
||||||
// #define CTLIOCGINFO _IOWR('N', 3, struct ctl_info) /* get id from name */ = 0xc0644e03
|
|
||||||
const _CTLIOCGINFO = iocInout | ((100 & 0x1fff) << 16) | uint32(byte('N'))<<8 | 3
|
|
||||||
|
|
||||||
// #define SIOCPROTOATTACH_IN6 _IOWR('i', 110, struct in6_aliasreq_64)
|
|
||||||
const siocprotoattachIn6 = iocInout | ((128 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 110
|
|
||||||
|
|
||||||
// #define SIOCLL_START _IOWR('i', 130, struct in6Aliasreq)
|
|
||||||
const siocllStart = iocInout | ((128 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 130
|
|
||||||
|
|
||||||
// Following the wireguard-go solution:
|
|
||||||
// These unix.SYS_* constants were removed from golang.org/x/sys/unix
|
|
||||||
// so copy them here for now.
|
|
||||||
// See https://github.com/golang/go/issues/41868
|
|
||||||
const (
|
|
||||||
sysIoctl = 54
|
|
||||||
sysConnect = 98
|
|
||||||
sysGetsockopt = 118
|
|
||||||
)
|
|
||||||
|
|
||||||
type tunDarwin struct {
|
|
||||||
name string
|
|
||||||
tunAddress string
|
|
||||||
autoRoute bool
|
|
||||||
tunFile *os.File
|
|
||||||
errors chan error
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
stopOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
// sockaddr_ctl specifeid in /usr/include/sys/kern_control.h
|
|
||||||
type sockaddrCtl struct {
|
|
||||||
scLen uint8
|
|
||||||
scFamily uint8
|
|
||||||
ssSysaddr uint16
|
|
||||||
scID uint32
|
|
||||||
scUnit uint32
|
|
||||||
scReserved [5]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type ctlInfo struct {
|
|
||||||
ctlID uint32
|
|
||||||
ctlName [96]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/sys/sockio.h#L107
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/net/if.h#L570-L575
|
|
||||||
// https://man.openbsd.org/netintro.4#SIOCAIFADDR
|
|
||||||
type aliasreq struct {
|
|
||||||
ifraName [unix.IFNAMSIZ]byte
|
|
||||||
ifraAddr unix.RawSockaddrInet4
|
|
||||||
ifraDstaddr unix.RawSockaddrInet4
|
|
||||||
ifraMask unix.RawSockaddrInet4
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIOCAIFADDR_IN6
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/in6_var.h#L114-L119
|
|
||||||
// https://opensource.apple.com/source/network_cmds/network_cmds-543.260.3/
|
|
||||||
type in6Addrlifetime struct{}
|
|
||||||
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/in6_var.h#L336-L343
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/in6.h#L174-L181
|
|
||||||
type in6Aliasreq struct {
|
|
||||||
ifraName [unix.IFNAMSIZ]byte
|
|
||||||
ifraAddr unix.RawSockaddrInet6
|
|
||||||
ifraDstaddr unix.RawSockaddrInet6
|
|
||||||
ifraPrefixmask unix.RawSockaddrInet6
|
|
||||||
ifraFlags int32
|
|
||||||
ifraLifetime in6Addrlifetime
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/net/if.h#L402-L563
|
|
||||||
|
|
||||||
//type ifreqAddr struct {
|
|
||||||
// Name [unix.IFNAMSIZ]byte
|
|
||||||
// Addr unix.RawSockaddrInet4
|
|
||||||
// Pad [8]byte
|
|
||||||
//}
|
|
||||||
|
|
||||||
var sockaddrCtlSize uintptr = 32
|
|
||||||
|
|
||||||
// OpenTunDevice return a TunDevice according a URL
|
|
||||||
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
||||||
name := "utun"
|
|
||||||
mtu := 9000
|
|
||||||
|
|
||||||
ifIndex := -1
|
|
||||||
if name != "utun" {
|
|
||||||
_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
|
|
||||||
if err != nil || ifIndex < 0 {
|
|
||||||
return nil, fmt.Errorf("interface name must be utun[0-9]*")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctlInfo1 := &ctlInfo{}
|
|
||||||
|
|
||||||
copy(ctlInfo1.ctlName[:], []byte(utunControlName))
|
|
||||||
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
sysIoctl,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(_CTLIOCGINFO),
|
|
||||||
uintptr(unsafe.Pointer(ctlInfo1)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, fmt.Errorf("_CTLIOCGINFO: %v", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := sockaddrCtl{
|
|
||||||
scLen: uint8(sockaddrCtlSize),
|
|
||||||
scFamily: unix.AF_SYSTEM,
|
|
||||||
ssSysaddr: 2,
|
|
||||||
scID: ctlInfo1.ctlID,
|
|
||||||
scUnit: uint32(ifIndex) + 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
scPointer := unsafe.Pointer(&sc)
|
|
||||||
|
|
||||||
_, _, errno = unix.RawSyscall(
|
|
||||||
sysConnect,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(scPointer),
|
|
||||||
uintptr(sockaddrCtlSize),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, fmt.Errorf("SYS_CONNECT: %v", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = syscall.SetNonblock(fd, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), ""), mtu, tunAddress, autoRoute)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if autoRoute {
|
|
||||||
setAutoRoute(tunAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tun, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTUNFromFile(file *os.File, mtu int, tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
||||||
tun := &tunDarwin{
|
|
||||||
tunFile: file,
|
|
||||||
tunAddress: tunAddress,
|
|
||||||
autoRoute: autoRoute,
|
|
||||||
errors: make(chan error, 5),
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := tun.getName()
|
|
||||||
if err != nil {
|
|
||||||
tun.tunFile.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tun.name = name
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
tun.tunFile.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if mtu > 0 {
|
|
||||||
err = tun.setMTU(mtu)
|
|
||||||
if err != nil {
|
|
||||||
tun.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This address doesn't mean anything here. NIC just net an IP address to set route upon.
|
|
||||||
p2pAddress := net.ParseIP(tunAddress)
|
|
||||||
err = tun.setTunAddress(p2pAddress)
|
|
||||||
if err != nil {
|
|
||||||
tun.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tun.attachLinkLocal()
|
|
||||||
if err != nil {
|
|
||||||
tun.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tun, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) Name() string {
|
|
||||||
return t.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) URL() string {
|
|
||||||
return fmt.Sprintf("dev://%s", t.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) MTU() (int, error) {
|
|
||||||
return t.getInterfaceMtu()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) Read(buff []byte) (int, error) {
|
|
||||||
select {
|
|
||||||
case err := <-t.errors:
|
|
||||||
return 0, err
|
|
||||||
default:
|
|
||||||
n, err := t.tunFile.Read(buff)
|
|
||||||
if n < 4 {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(buff[:], buff[4:])
|
|
||||||
return n - 4, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) Write(buff []byte) (int, error) {
|
|
||||||
// reserve space for header
|
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
|
||||||
defer pool.Put(buf[:cap(buf)])
|
|
||||||
|
|
||||||
buf[0] = 0x00
|
|
||||||
buf[1] = 0x00
|
|
||||||
buf[2] = 0x00
|
|
||||||
|
|
||||||
copy(buf[4:], buff)
|
|
||||||
if buf[4]>>4 == ipv6.Version {
|
|
||||||
buf[3] = unix.AF_INET6
|
|
||||||
} else {
|
|
||||||
buf[3] = unix.AF_INET
|
|
||||||
}
|
|
||||||
|
|
||||||
// write
|
|
||||||
return t.tunFile.Write(buf[:4+len(buff)])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) IsClose() bool {
|
|
||||||
return t.closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) Close() error {
|
|
||||||
t.stopOnce.Do(func() {
|
|
||||||
if t.autoRoute {
|
|
||||||
resetAutoRoute(t.tunAddress)
|
|
||||||
}
|
|
||||||
t.closed = true
|
|
||||||
t.tunFile.Close()
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) getInterfaceMtu() (int, error) {
|
|
||||||
// open datagram socket
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer unix.Close(fd)
|
|
||||||
|
|
||||||
// do ioctl call
|
|
||||||
|
|
||||||
var ifr [64]byte
|
|
||||||
copy(ifr[:], t.name)
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
sysIoctl,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCGIFMTU),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, fmt.Errorf("failed to get MTU on %s", t.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(*(*int32)(unsafe.Pointer(&ifr[16]))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) getName() (string, error) {
|
|
||||||
var ifName struct {
|
|
||||||
name [16]byte
|
|
||||||
}
|
|
||||||
ifNameSize := uintptr(16)
|
|
||||||
|
|
||||||
var errno syscall.Errno
|
|
||||||
t.operateOnFd(func(fd uintptr) {
|
|
||||||
_, _, errno = unix.Syscall6(
|
|
||||||
sysGetsockopt,
|
|
||||||
fd,
|
|
||||||
2, /* #define SYSPROTO_CONTROL 2 */
|
|
||||||
2, /* #define UTUN_OPT_IFNAME 2 */
|
|
||||||
uintptr(unsafe.Pointer(&ifName)),
|
|
||||||
uintptr(unsafe.Pointer(&ifNameSize)), 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return "", fmt.Errorf("SYS_GETSOCKOPT: %v", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.name = string(ifName.name[:ifNameSize-1])
|
|
||||||
return t.name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) setMTU(n int) error {
|
|
||||||
// open datagram socket
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer unix.Close(fd)
|
|
||||||
|
|
||||||
// do ioctl call
|
|
||||||
|
|
||||||
var ifr [32]byte
|
|
||||||
copy(ifr[:], t.name)
|
|
||||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
sysIoctl,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCSIFMTU),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return fmt.Errorf("failed to set MTU on %s", t.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) operateOnFd(fn func(fd uintptr)) {
|
|
||||||
sysconn, err := t.tunFile.SyscallConn()
|
|
||||||
// TODO: consume the errors
|
|
||||||
if err != nil {
|
|
||||||
t.errors <- fmt.Errorf("unable to find sysconn for tunfile: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = sysconn.Control(fn)
|
|
||||||
if err != nil {
|
|
||||||
t.errors <- fmt.Errorf("unable to control sysconn for tunfile: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) setTunAddress(addr net.IP) error {
|
|
||||||
var ifr [unix.IFNAMSIZ]byte
|
|
||||||
copy(ifr[:], t.name)
|
|
||||||
|
|
||||||
// set IPv4 address
|
|
||||||
fd4, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer syscall.Close(fd4)
|
|
||||||
|
|
||||||
var ip4 [4]byte
|
|
||||||
copy(ip4[:], addr.To4())
|
|
||||||
ip4mask := [4]byte{255, 255, 0, 0}
|
|
||||||
ifra4 := aliasreq{
|
|
||||||
ifraName: ifr,
|
|
||||||
ifraAddr: unix.RawSockaddrInet4{
|
|
||||||
Len: unix.SizeofSockaddrInet4,
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Addr: ip4,
|
|
||||||
},
|
|
||||||
ifraDstaddr: unix.RawSockaddrInet4{
|
|
||||||
Len: unix.SizeofSockaddrInet4,
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Addr: ip4,
|
|
||||||
},
|
|
||||||
ifraMask: unix.RawSockaddrInet4{
|
|
||||||
Len: unix.SizeofSockaddrInet4,
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Addr: ip4mask,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, errno := unix.Syscall(
|
|
||||||
sysIoctl,
|
|
||||||
uintptr(fd4),
|
|
||||||
uintptr(unix.SIOCAIFADDR),
|
|
||||||
uintptr(unsafe.Pointer(&ifra4)),
|
|
||||||
); errno != 0 {
|
|
||||||
return fmt.Errorf("failed to set ip address on %s: %v", t.name, errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunDarwin) attachLinkLocal() error {
|
|
||||||
var ifr [unix.IFNAMSIZ]byte
|
|
||||||
copy(ifr[:], t.name)
|
|
||||||
|
|
||||||
// attach link-local address
|
|
||||||
fd6, err := unix.Socket(
|
|
||||||
unix.AF_INET6,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer syscall.Close(fd6)
|
|
||||||
|
|
||||||
// Attach link-local address
|
|
||||||
ifra6 := in6Aliasreq{
|
|
||||||
ifraName: ifr,
|
|
||||||
}
|
|
||||||
if _, _, errno := unix.Syscall(
|
|
||||||
sysIoctl,
|
|
||||||
uintptr(fd6),
|
|
||||||
uintptr(siocprotoattachIn6),
|
|
||||||
uintptr(unsafe.Pointer(&ifra6)),
|
|
||||||
); errno != 0 {
|
|
||||||
return fmt.Errorf("failed to attach link-local address on %s: SIOCPROTOATTACH_IN6 %v", t.name, errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, errno := unix.Syscall(
|
|
||||||
sysIoctl,
|
|
||||||
uintptr(fd6),
|
|
||||||
uintptr(siocllStart),
|
|
||||||
uintptr(unsafe.Pointer(&ifra6)),
|
|
||||||
); errno != 0 {
|
|
||||||
return fmt.Errorf("failed to set ipv6 address on %s: SIOCLL_START %v", t.name, errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAutoDetectInterface get ethernet interface
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
cmd := exec.Command("route", "-n", "get", "default")
|
|
||||||
if result, err := cmd.Output(); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
resultString := string(result)
|
|
||||||
reg, err := regexp.Compile("(interface:)(.*)")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
matchResult := reg.FindStringSubmatch(resultString)
|
|
||||||
interfaceName := strings.TrimSpace(matchResult[len(matchResult)-1])
|
|
||||||
return interfaceName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func setAutoRoute(tunGateway string) {
|
|
||||||
addRoute("1", tunGateway)
|
|
||||||
addRoute("2/7", tunGateway)
|
|
||||||
addRoute("4/6", tunGateway)
|
|
||||||
addRoute("8/5", tunGateway)
|
|
||||||
addRoute("16/4", tunGateway)
|
|
||||||
addRoute("32/3", tunGateway)
|
|
||||||
addRoute("64/2", tunGateway)
|
|
||||||
addRoute("128.0/1", tunGateway)
|
|
||||||
addRoute("198.18.0/16", tunGateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetAutoRoute(tunGateway string) {
|
|
||||||
delRoute("1", tunGateway)
|
|
||||||
delRoute("2/7", tunGateway)
|
|
||||||
delRoute("4/6", tunGateway)
|
|
||||||
delRoute("8/5", tunGateway)
|
|
||||||
delRoute("16/4", tunGateway)
|
|
||||||
delRoute("32/3", tunGateway)
|
|
||||||
delRoute("64/2", tunGateway)
|
|
||||||
delRoute("128.0/1", tunGateway)
|
|
||||||
delRoute("198.18.0/16", tunGateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRoute(net, name string) {
|
|
||||||
cmd := exec.Command("route", "add", "-net", net, name)
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func delRoute(net, name string) {
|
|
||||||
cmd := exec.Command("route", "delete", "-net", net, name)
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,349 +0,0 @@
|
|||||||
//go:build linux || android
|
|
||||||
// +build linux android
|
|
||||||
|
|
||||||
package dev
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cloneDevicePath = "/dev/net/tun"
|
|
||||||
ifReqSize = unix.IFNAMSIZ + 64
|
|
||||||
)
|
|
||||||
|
|
||||||
type tunLinux struct {
|
|
||||||
url string
|
|
||||||
name string
|
|
||||||
tunAddress string
|
|
||||||
autoRoute bool
|
|
||||||
tunFile *os.File
|
|
||||||
mtu int
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
stopOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenTunDevice return a TunDevice according a URL
|
|
||||||
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
||||||
deviceURL, _ := url.Parse("dev://utun")
|
|
||||||
mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32)
|
|
||||||
|
|
||||||
t := &tunLinux{
|
|
||||||
url: deviceURL.String(),
|
|
||||||
mtu: int(mtu),
|
|
||||||
tunAddress: tunAddress,
|
|
||||||
autoRoute: autoRoute,
|
|
||||||
}
|
|
||||||
switch deviceURL.Scheme {
|
|
||||||
case "dev":
|
|
||||||
var err error
|
|
||||||
var dev TunDevice
|
|
||||||
dev, err = t.openDeviceByName(deviceURL.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.configInterface()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if autoRoute {
|
|
||||||
addRoute(tunAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dev, nil
|
|
||||||
case "fd":
|
|
||||||
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var dev TunDevice
|
|
||||||
dev, err = t.openDeviceByFd(int(fd))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if autoRoute {
|
|
||||||
log.Warnln("linux unsupported automatic route")
|
|
||||||
}
|
|
||||||
return dev, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unsupported device type `%s`", deviceURL.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) Name() string {
|
|
||||||
return t.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) URL() string {
|
|
||||||
return t.url
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) Write(buff []byte) (int, error) {
|
|
||||||
return t.tunFile.Write(buff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) Read(buff []byte) (int, error) {
|
|
||||||
return t.tunFile.Read(buff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) IsClose() bool {
|
|
||||||
return t.closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) Close() error {
|
|
||||||
t.stopOnce.Do(func() {
|
|
||||||
t.closed = true
|
|
||||||
t.tunFile.Close()
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) MTU() (int, error) {
|
|
||||||
// Sometime, we can't read MTU by SIOCGIFMTU. Then we should return the preset MTU
|
|
||||||
if t.mtu > 0 {
|
|
||||||
return t.mtu, nil
|
|
||||||
}
|
|
||||||
mtu, err := t.getInterfaceMtu()
|
|
||||||
return int(mtu), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) openDeviceByName(name string) (TunDevice, error) {
|
|
||||||
nfd, err := unix.Open(cloneDevicePath, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ifr [ifReqSize]byte
|
|
||||||
var flags uint16 = unix.IFF_TUN | unix.IFF_NO_PI
|
|
||||||
nameBytes := []byte(name)
|
|
||||||
if len(nameBytes) >= unix.IFNAMSIZ {
|
|
||||||
return nil, errors.New("interface name too long")
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(ifr[:], nameBytes)
|
|
||||||
|
|
||||||
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags
|
|
||||||
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(nfd),
|
|
||||||
uintptr(unix.TUNSETIFF),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unix.SetNonblock(nfd, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Note that the above -- open,ioctl,nonblock -- must happen prior to handing it to netpoll as below this line.
|
|
||||||
|
|
||||||
t.tunFile = os.NewFile(uintptr(nfd), cloneDevicePath)
|
|
||||||
t.name, err = t.getName()
|
|
||||||
if err != nil {
|
|
||||||
t.tunFile.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) configInterface() error {
|
|
||||||
var ifr [ifReqSize]byte
|
|
||||||
nameBytes := []byte(t.name)
|
|
||||||
if len(nameBytes) >= unix.IFNAMSIZ {
|
|
||||||
return errors.New("interface name too long")
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(ifr[:], nameBytes)
|
|
||||||
|
|
||||||
fd, _, errno := syscall.Syscall(unix.SYS_SOCKET, unix.AF_INET, unix.SOCK_STREAM, 0)
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
// set addr for tun
|
|
||||||
var ip []byte
|
|
||||||
for _, num := range strings.Split(t.tunAddress, ".") {
|
|
||||||
value, err := strconv.Atoi(num)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ip = append(ip, byte(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint16(unix.AF_INET)
|
|
||||||
|
|
||||||
copy(ifr[unix.IFNAMSIZ+4:], ip)
|
|
||||||
|
|
||||||
_, _, errno = unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
fd,
|
|
||||||
uintptr(unix.SIOCSIFADDR),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])))
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
// set netmask for tun
|
|
||||||
netmask := []byte{255, 255, 0, 0}
|
|
||||||
copy(ifr[unix.IFNAMSIZ+4:], netmask)
|
|
||||||
|
|
||||||
_, _, errno = unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
fd,
|
|
||||||
uintptr(unix.SIOCSIFNETMASK),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])))
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface up
|
|
||||||
_, _, errno = syscall.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.SIOCSIFFLAGS), uintptr(unsafe.Pointer(&ifr[0])))
|
|
||||||
|
|
||||||
var flags = uint16(unix.IFF_UP | unix.IFF_TUN | unix.IFF_MULTICAST | unix.IFF_RUNNING | unix.IFF_NOARP)
|
|
||||||
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags
|
|
||||||
|
|
||||||
_, _, errno = syscall.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
fd,
|
|
||||||
uintptr(unix.SIOCSIFFLAGS),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])))
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) openDeviceByFd(fd int) (TunDevice, error) {
|
|
||||||
var ifr struct {
|
|
||||||
name [16]byte
|
|
||||||
flags uint16
|
|
||||||
_ [22]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := syscall.Dup(fd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNGETIFF, uintptr(unsafe.Pointer(&ifr)))
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
if ifr.flags&syscall.IFF_TUN == 0 || ifr.flags&syscall.IFF_NO_PI == 0 {
|
|
||||||
return nil, errors.New("only tun device and no pi mode supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
nullStr := ifr.name[:]
|
|
||||||
i := bytes.IndexByte(nullStr, 0)
|
|
||||||
if i != -1 {
|
|
||||||
nullStr = nullStr[:i]
|
|
||||||
}
|
|
||||||
t.name = string(nullStr)
|
|
||||||
t.tunFile = os.NewFile(uintptr(fd), "/dev/tun")
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) getInterfaceMtu() (uint32, error) {
|
|
||||||
fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer syscall.Close(fd)
|
|
||||||
|
|
||||||
var ifreq struct {
|
|
||||||
name [16]byte
|
|
||||||
mtu int32
|
|
||||||
_ [20]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(ifreq.name[:], t.name)
|
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCGIFMTU, uintptr(unsafe.Pointer(&ifreq)))
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(ifreq.mtu), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tunLinux) getName() (string, error) {
|
|
||||||
sysconn, err := t.tunFile.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var ifr [ifReqSize]byte
|
|
||||||
var errno syscall.Errno
|
|
||||||
err = sysconn.Control(func(fd uintptr) {
|
|
||||||
_, _, errno = unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
fd,
|
|
||||||
uintptr(unix.TUNGETIFF),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New("failed to get name of TUN device: " + err.Error())
|
|
||||||
}
|
|
||||||
if errno != 0 {
|
|
||||||
return "", errors.New("failed to get name of TUN device: " + errno.Error())
|
|
||||||
}
|
|
||||||
nullStr := ifr[:]
|
|
||||||
i := bytes.IndexByte(nullStr, 0)
|
|
||||||
if i != -1 {
|
|
||||||
nullStr = nullStr[:i]
|
|
||||||
}
|
|
||||||
t.name = string(nullStr)
|
|
||||||
return t.name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAutoDetectInterface get ethernet interface
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
cmd := exec.Command("bash", "-c", "ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return out.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRoute(gateway string) {
|
|
||||||
cmd := exec.Command("route", "add", "default", "gw", gateway)
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func delRoute(gateway string) {
|
|
||||||
cmd := exec.Command("ip", "route", "delete", "gw")
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
//go:build !linux && !android && !darwin && !windows
|
|
||||||
// +build !linux,!android,!darwin,!windows
|
|
||||||
|
|
||||||
package dev
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func OpenTunDevice(tunAddress string, autoRute bool) (TunDevice, error) {
|
|
||||||
return nil, errors.New("Unsupported platform " + runtime.GOOS + "/" + runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAutoDetectInterface get ethernet interface
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
// Modified from: https://git.zx2c4.com/wireguard-go/tree/tun/tun_windows.go and https://git.zx2c4.com/wireguard-windows/tree/tunnel/addressconfig.go
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package dev
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
_ "unsafe"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/dev/wintun"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond)
|
|
||||||
spinloopRateThreshold = 800000000 / 8 // 800mbps
|
|
||||||
spinloopDuration = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s
|
|
||||||
)
|
|
||||||
|
|
||||||
type rateJuggler struct {
|
|
||||||
current uint64
|
|
||||||
nextByteCount uint64
|
|
||||||
nextStartTime int64
|
|
||||||
changing int32
|
|
||||||
}
|
|
||||||
|
|
||||||
var WintunTunnelType = "Meta"
|
|
||||||
var WintunStaticRequestedGUID *windows.GUID
|
|
||||||
|
|
||||||
//go:linkname procyield runtime.procyield
|
|
||||||
func procyield(cycles uint32)
|
|
||||||
|
|
||||||
//go:linkname nanotime runtime.nanotime
|
|
||||||
func nanotime() int64
|
|
||||||
|
|
||||||
func (tun *tunWindows) Close() error {
|
|
||||||
var err error
|
|
||||||
tun.closeOnce.Do(func() {
|
|
||||||
atomic.StoreInt32(&tun.close, 1)
|
|
||||||
windows.SetEvent(tun.readWait)
|
|
||||||
//tun.running.Wait()
|
|
||||||
tun.session.End()
|
|
||||||
if tun.wt != nil {
|
|
||||||
err = tun.wt.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) MTU() (int, error) {
|
|
||||||
return tun.forcedMTU, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is a temporary hack. We really need to be monitoring the interface in real time and adapting to MTU changes.
|
|
||||||
func (tun *tunWindows) ForceMTU(mtu int) {
|
|
||||||
tun.forcedMTU = mtu
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking.
|
|
||||||
|
|
||||||
func (tun *tunWindows) Read0(buff []byte, offset int) (int, error) {
|
|
||||||
tun.running.Add(1)
|
|
||||||
|
|
||||||
defer tun.running.Done()
|
|
||||||
retry:
|
|
||||||
if atomic.LoadInt32(&tun.close) == 1 {
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
}
|
|
||||||
start := nanotime()
|
|
||||||
shouldSpin := atomic.LoadUint64(&tun.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&tun.rate.nextStartTime)) <= rateMeasurementGranularity*2
|
|
||||||
for {
|
|
||||||
if atomic.LoadInt32(&tun.close) == 1 {
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
}
|
|
||||||
packet, err := tun.session.ReceivePacket()
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
packetSize := len(packet)
|
|
||||||
copy(buff[offset:], packet)
|
|
||||||
tun.session.ReleaseReceivePacket(packet)
|
|
||||||
tun.rate.update(uint64(packetSize))
|
|
||||||
return packetSize, nil
|
|
||||||
case windows.ERROR_NO_MORE_ITEMS:
|
|
||||||
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
|
|
||||||
windows.WaitForSingleObject(tun.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) Flush() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) Write0(buff []byte, offset int) (int, error) {
|
|
||||||
tun.running.Add(1)
|
|
||||||
defer tun.running.Done()
|
|
||||||
if atomic.LoadInt32(&tun.close) == 1 {
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
packetSize := len(buff) - offset
|
|
||||||
tun.rate.update(uint64(packetSize))
|
|
||||||
|
|
||||||
packet, err := tun.session.AllocateSendPacket(packetSize)
|
|
||||||
if err == nil {
|
|
||||||
copy(packet, buff[offset:])
|
|
||||||
tun.session.SendPacket(packet)
|
|
||||||
return packetSize, nil
|
|
||||||
}
|
|
||||||
switch err {
|
|
||||||
case windows.ERROR_HANDLE_EOF:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
case windows.ERROR_BUFFER_OVERFLOW:
|
|
||||||
return 0, nil // Dropping when ring is full.
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("Write failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LUID returns Windows interface instance ID.
|
|
||||||
func (tun *tunWindows) LUID() uint64 {
|
|
||||||
tun.running.Add(1)
|
|
||||||
defer tun.running.Done()
|
|
||||||
if atomic.LoadInt32(&tun.close) == 1 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return tun.wt.LUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunningVersion returns the running version of the Wintun driver.
|
|
||||||
func (tun *tunWindows) RunningVersion() (version uint32, err error) {
|
|
||||||
return wintun.RunningVersion()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rate *rateJuggler) update(packetLen uint64) {
|
|
||||||
now := nanotime()
|
|
||||||
total := atomic.AddUint64(&rate.nextByteCount, packetLen)
|
|
||||||
period := uint64(now - atomic.LoadInt64(&rate.nextStartTime))
|
|
||||||
if period >= rateMeasurementGranularity {
|
|
||||||
if !atomic.CompareAndSwapInt32(&rate.changing, 0, 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
atomic.StoreInt64(&rate.nextStartTime, now)
|
|
||||||
atomic.StoreUint64(&rate.current, total*uint64(time.Second/time.Nanosecond)/period)
|
|
||||||
atomic.StoreUint64(&rate.nextByteCount, 0)
|
|
||||||
atomic.StoreInt32(&rate.changing, 0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,398 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package dev
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/dev/wintun"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
const messageTransportHeaderSize = 0 // size of data preceding content in transport message
|
|
||||||
|
|
||||||
type tunWindows struct {
|
|
||||||
wt *wintun.Adapter
|
|
||||||
handle windows.Handle
|
|
||||||
close int32
|
|
||||||
running sync.WaitGroup
|
|
||||||
forcedMTU int
|
|
||||||
rate rateJuggler
|
|
||||||
session wintun.Session
|
|
||||||
readWait windows.Handle
|
|
||||||
closeOnce sync.Once
|
|
||||||
|
|
||||||
url string
|
|
||||||
name string
|
|
||||||
tunAddress string
|
|
||||||
autoRoute bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenTunDevice return a TunDevice according a URL
|
|
||||||
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
||||||
|
|
||||||
requestedGUID, err := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}")
|
|
||||||
if err == nil {
|
|
||||||
WintunStaticRequestedGUID = &requestedGUID
|
|
||||||
log.Debugln("Generate GUID: %s", WintunStaticRequestedGUID.String())
|
|
||||||
} else {
|
|
||||||
log.Warnln("Error parese GUID from string: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaceName := "Meta Tunnel"
|
|
||||||
mtu := 9000
|
|
||||||
|
|
||||||
tun, err := CreateTUN(interfaceName, mtu, tunAddress, autoRoute)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tun, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// CreateTUN creates a Wintun interface with the given name. Should a Wintun
|
|
||||||
// interface with the same name exist, it is reused.
|
|
||||||
//
|
|
||||||
func CreateTUN(ifname string, mtu int, tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
||||||
return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu, tunAddress, autoRoute)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and
|
|
||||||
// a requested GUID. Should a Wintun interface with the same name exist, it is reused.
|
|
||||||
//
|
|
||||||
func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int, tunAddress string, autoRoute bool) (TunDevice, error) {
|
|
||||||
wt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error creating interface: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
forcedMTU := 1420
|
|
||||||
if mtu > 0 {
|
|
||||||
forcedMTU = mtu
|
|
||||||
}
|
|
||||||
|
|
||||||
tun := &tunWindows{
|
|
||||||
name: ifname,
|
|
||||||
wt: wt,
|
|
||||||
handle: windows.InvalidHandle,
|
|
||||||
forcedMTU: forcedMTU,
|
|
||||||
tunAddress: tunAddress,
|
|
||||||
autoRoute: autoRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
// config tun ip
|
|
||||||
err = tun.configureInterface()
|
|
||||||
if err != nil {
|
|
||||||
tun.wt.Close()
|
|
||||||
return nil, fmt.Errorf("error configure interface: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB
|
|
||||||
if err != nil {
|
|
||||||
tun.wt.Close()
|
|
||||||
return nil, fmt.Errorf("error starting session: %w", err)
|
|
||||||
}
|
|
||||||
tun.readWait = tun.session.ReadWaitEvent()
|
|
||||||
return tun, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) Name() string {
|
|
||||||
return tun.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) IsClose() bool {
|
|
||||||
return atomic.LoadInt32(&tun.close) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) Read(buff []byte) (int, error) {
|
|
||||||
return tun.Read0(buff, messageTransportHeaderSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) Write(buff []byte) (int, error) {
|
|
||||||
return tun.Write0(buff, messageTransportHeaderSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) URL() string {
|
|
||||||
return fmt.Sprintf("dev://%s", tun.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *tunWindows) configureInterface() error {
|
|
||||||
retryOnFailure := wintun.StartedAtBoot()
|
|
||||||
tryTimes := 0
|
|
||||||
startOver:
|
|
||||||
var err error
|
|
||||||
if tryTimes > 0 {
|
|
||||||
log.Infoln("Retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
retryOnFailure = retryOnFailure && tryTimes < 15
|
|
||||||
}
|
|
||||||
tryTimes++
|
|
||||||
|
|
||||||
luid := winipcfg.LUID(tun.LUID())
|
|
||||||
log.Infoln("[wintun]: tun adapter LUID: %d", luid)
|
|
||||||
mtu, err := tun.MTU()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("unable to get device mtu")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := winipcfg.AddressFamily(windows.AF_INET)
|
|
||||||
familyV6 := winipcfg.AddressFamily(windows.AF_INET6)
|
|
||||||
|
|
||||||
tunAddress := wintun.ParseIPCidr(tun.tunAddress + "/16")
|
|
||||||
|
|
||||||
addresses := []net.IPNet{tunAddress.IPNet()}
|
|
||||||
|
|
||||||
err = luid.FlushIPAddresses(familyV6)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = luid.FlushDNS(family)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = luid.FlushDNS(familyV6)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = luid.FlushRoutes(familyV6)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
foundDefault4 := false
|
|
||||||
foundDefault6 := false
|
|
||||||
|
|
||||||
if tun.autoRoute {
|
|
||||||
allowedIPs := []*wintun.IPCidr{
|
|
||||||
//wintun.ParseIPCidr("0.0.0.0/0"),
|
|
||||||
wintun.ParseIPCidr("1.0.0.0/8"),
|
|
||||||
wintun.ParseIPCidr("2.0.0.0/7"),
|
|
||||||
wintun.ParseIPCidr("4.0.0.0/6"),
|
|
||||||
wintun.ParseIPCidr("8.0.0.0/5"),
|
|
||||||
wintun.ParseIPCidr("16.0.0.0/4"),
|
|
||||||
wintun.ParseIPCidr("32.0.0.0/3"),
|
|
||||||
wintun.ParseIPCidr("64.0.0.0/2"),
|
|
||||||
wintun.ParseIPCidr("128.0.0.0/1"),
|
|
||||||
wintun.ParseIPCidr("224.0.0.0/4"),
|
|
||||||
wintun.ParseIPCidr("255.255.255.255/32"),
|
|
||||||
}
|
|
||||||
|
|
||||||
estimatedRouteCount := len(allowedIPs)
|
|
||||||
routes := make([]winipcfg.RouteData, 0, estimatedRouteCount)
|
|
||||||
var haveV4Address, haveV6Address bool = true, false
|
|
||||||
|
|
||||||
for _, allowedip := range allowedIPs {
|
|
||||||
allowedip.MaskSelf()
|
|
||||||
if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
route := winipcfg.RouteData{
|
|
||||||
Destination: allowedip.IPNet(),
|
|
||||||
Metric: 0,
|
|
||||||
}
|
|
||||||
if allowedip.Bits() == 32 {
|
|
||||||
if allowedip.Cidr == 0 {
|
|
||||||
foundDefault4 = true
|
|
||||||
}
|
|
||||||
route.NextHop = net.IPv4zero
|
|
||||||
} else if allowedip.Bits() == 128 {
|
|
||||||
if allowedip.Cidr == 0 {
|
|
||||||
foundDefault6 = true
|
|
||||||
}
|
|
||||||
route.NextHop = net.IPv6zero
|
|
||||||
}
|
|
||||||
routes = append(routes, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
|
|
||||||
sort.Slice(routes, func(i, j int) bool {
|
|
||||||
if routes[i].Metric != routes[j].Metric {
|
|
||||||
return routes[i].Metric < routes[j].Metric
|
|
||||||
}
|
|
||||||
if c := bytes.Compare(routes[i].NextHop, routes[j].NextHop); c != 0 {
|
|
||||||
return c < 0
|
|
||||||
}
|
|
||||||
if c := bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP); c != 0 {
|
|
||||||
return c < 0
|
|
||||||
}
|
|
||||||
if c := bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask); c != 0 {
|
|
||||||
return c < 0
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
for i := 0; i < len(routes); i++ {
|
|
||||||
if i > 0 && routes[i].Metric == routes[i-1].Metric &&
|
|
||||||
bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) &&
|
|
||||||
bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) &&
|
|
||||||
bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
deduplicatedRoutes = append(deduplicatedRoutes, &routes[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
err = luid.SetRoutesForFamily(family, deduplicatedRoutes)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set routes: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = luid.SetIPAddressesForFamily(family, addresses)
|
|
||||||
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
|
||||||
cleanupAddressesOnDisconnectedInterfaces(family, addresses)
|
|
||||||
err = luid.SetIPAddressesForFamily(family, addresses)
|
|
||||||
}
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set ips: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipif *winipcfg.MibIPInterfaceRow
|
|
||||||
ipif, err = luid.IPInterface(family)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
||||||
ipif.DadTransmits = 0
|
|
||||||
ipif.ManagedAddressConfigurationSupported = false
|
|
||||||
ipif.OtherStatefulConfigurationSupported = false
|
|
||||||
if mtu > 0 {
|
|
||||||
ipif.NLMTU = uint32(mtu)
|
|
||||||
}
|
|
||||||
if (family == windows.AF_INET && foundDefault4) || (family == windows.AF_INET6 && foundDefault6) {
|
|
||||||
ipif.UseAutomaticMetric = false
|
|
||||||
ipif.Metric = 0
|
|
||||||
}
|
|
||||||
err = ipif.Set()
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set metric and MTU: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipif6 *winipcfg.MibIPInterfaceRow
|
|
||||||
ipif6, err = luid.IPInterface(familyV6)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
||||||
ipif6.DadTransmits = 0
|
|
||||||
ipif6.ManagedAddressConfigurationSupported = false
|
|
||||||
ipif6.OtherStatefulConfigurationSupported = false
|
|
||||||
|
|
||||||
err = ipif6.Set()
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set v6 metric and MTU: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = luid.SetDNS(family, []net.IP{net.ParseIP("198.18.0.2")}, nil)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set DNS %s %s: %w", "198.18.0.2", "nil", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
|
|
||||||
if len(addresses) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addrToStr := func(ip *net.IP) string {
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
return string(ip4)
|
|
||||||
}
|
|
||||||
return string(*ip)
|
|
||||||
}
|
|
||||||
addrHash := make(map[string]bool, len(addresses))
|
|
||||||
for i := range addresses {
|
|
||||||
addrHash[addrToStr(&addresses[i].IP)] = true
|
|
||||||
}
|
|
||||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.OperStatus == winipcfg.IfOperStatusUp {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
|
||||||
ip := address.Address.IP()
|
|
||||||
if addrHash[addrToStr(&ip)] {
|
|
||||||
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
|
|
||||||
log.Infoln("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName())
|
|
||||||
iface.LUID.DeleteIPAddress(ipnet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAutoDetectInterface get ethernet interface
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
|
||||||
if err == nil {
|
|
||||||
return ifname, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
|
||||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get ethernet interface failure. %w", err)
|
|
||||||
}
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.OperStatus != winipcfg.IfOperStatusUp {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ifname := iface.FriendlyName()
|
|
||||||
if ifname == "Clash" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
|
||||||
nextHop := gatewayAddress.Address.IP()
|
|
||||||
|
|
||||||
var ipnet net.IPNet
|
|
||||||
if family == windows.AF_INET {
|
|
||||||
ipnet = net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)}
|
|
||||||
} else {
|
|
||||||
ipnet = net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = iface.LUID.Route(ipnet, nextHop); err == nil {
|
|
||||||
return ifname, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("ethernet interface not found")
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wintun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.org/x/sys/windows/svc"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
startedAtBoot bool
|
|
||||||
startedAtBootOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartedAtBoot() bool {
|
|
||||||
startedAtBootOnce.Do(func() {
|
|
||||||
if isService, err := svc.IsWindowsService(); err == nil && !isService {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if reason, err := svc.DynamicStartReason(); err == nil {
|
|
||||||
startedAtBoot = (reason&svc.StartReasonAuto) != 0 || (reason&svc.StartReasonDelayedAuto) != 0
|
|
||||||
} else if errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
|
|
||||||
// TODO: Below this line is Windows 7 compatibility code, which hopefully we can delete at some point.
|
|
||||||
startedAtBoot = windows.DurationSinceBoot() < time.Minute*10
|
|
||||||
} else {
|
|
||||||
log.Printf("Unable to determine service start reason: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return startedAtBoot
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package wintun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IPCidr struct {
|
|
||||||
IP net.IP
|
|
||||||
Cidr uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IPCidr) String() string {
|
|
||||||
return fmt.Sprintf("%s/%d", r.IP.String(), r.Cidr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IPCidr) Bits() uint8 {
|
|
||||||
if r.IP.To4() != nil {
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
return 128
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IPCidr) IPNet() net.IPNet {
|
|
||||||
return net.IPNet{
|
|
||||||
IP: r.IP,
|
|
||||||
Mask: net.CIDRMask(int(r.Cidr), int(r.Bits())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IPCidr) MaskSelf() {
|
|
||||||
bits := int(r.Bits())
|
|
||||||
mask := net.CIDRMask(int(r.Cidr), bits)
|
|
||||||
for i := 0; i < bits/8; i++ {
|
|
||||||
r.IP[i] &= mask[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseIPCidr(ipcidr string) *IPCidr {
|
|
||||||
s := strings.Split(ipcidr, "/")
|
|
||||||
if len(s) != 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cidr, err := strconv.Atoi(s[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &IPCidr{
|
|
||||||
IP: net.ParseIP(s[0]),
|
|
||||||
Cidr: uint8(cidr),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wintun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/dev/wintun/embed_dll"
|
|
||||||
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
|
||||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
|
||||||
return &lazyProc{dll: d, Name: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
type lazyProc struct {
|
|
||||||
Name string
|
|
||||||
mu sync.Mutex
|
|
||||||
dll *lazyDLL
|
|
||||||
addr uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lazyProc) Find() error {
|
|
||||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
if p.addr != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := p.dll.Load()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error loading %v DLL: %w", p.dll.Name, err)
|
|
||||||
}
|
|
||||||
addr, err := p.nameToAddr()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error getting %v address: %w", p.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lazyProc) Addr() uintptr {
|
|
||||||
err := p.Find()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return p.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type lazyDLL struct {
|
|
||||||
Name string
|
|
||||||
mu sync.Mutex
|
|
||||||
module *memmod.Module
|
|
||||||
onLoad func(d *lazyDLL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *lazyDLL) Load() error {
|
|
||||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
if d.module != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
module, err := memmod.LoadLibrary(embed_dll.DDlContent)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to load library: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
|
||||||
if d.onLoad != nil {
|
|
||||||
d.onLoad(d)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
|
||||||
return p.dll.module.ProcAddressByName(p.Name)
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,21 +0,0 @@
|
|||||||
package embed_dll
|
|
||||||
|
|
||||||
// Copyright 2020 MeshStep Authors.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed x86/wintun.dll
|
|
||||||
var DDlContent []byte
|
|
@ -1,21 +0,0 @@
|
|||||||
package embed_dll
|
|
||||||
|
|
||||||
// Copyright 2020 MeshStep Authors.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed amd64/wintun.dll
|
|
||||||
var DDlContent []byte
|
|
@ -1,21 +0,0 @@
|
|||||||
package embed_dll
|
|
||||||
|
|
||||||
// Copyright 2020 MeshStep Authors.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed arm/wintun.dll
|
|
||||||
var DDlContent []byte
|
|
@ -1,21 +0,0 @@
|
|||||||
package embed_dll
|
|
||||||
|
|
||||||
// Copyright 2020 MeshStep Authors.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed arm64/wintun.dll
|
|
||||||
var DDlContent []byte
|
|
Binary file not shown.
@ -1,11 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
// Modified from: https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wintun
|
|
@ -1,90 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wintun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
handle uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
PacketSizeMax = 0xffff // Maximum packet size
|
|
||||||
RingCapacityMin = 0x20000 // Minimum ring capacity (128 kiB)
|
|
||||||
RingCapacityMax = 0x4000000 // Maximum ring capacity (64 MiB)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Packet with data
|
|
||||||
type Packet struct {
|
|
||||||
Next *Packet // Pointer to next packet in queue
|
|
||||||
Size uint32 // Size of packet (max WINTUN_MAX_IP_PACKET_SIZE)
|
|
||||||
Data *[PacketSizeMax]byte // Pointer to layer 3 IPv4 or IPv6 packet
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
|
|
||||||
procWintunEndSession = modwintun.NewProc("WintunEndSession")
|
|
||||||
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
|
|
||||||
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
|
|
||||||
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
|
||||||
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
|
||||||
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (wintun *Adapter) StartSession(capacity uint32) (session Session, err error) {
|
|
||||||
r0, _, e1 := syscall.Syscall(procWintunStartSession.Addr(), 2, uintptr(wintun.handle), uintptr(capacity), 0)
|
|
||||||
if r0 == 0 {
|
|
||||||
err = e1
|
|
||||||
} else {
|
|
||||||
session = Session{r0}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session Session) End() {
|
|
||||||
syscall.Syscall(procWintunEndSession.Addr(), 1, session.handle, 0, 0)
|
|
||||||
session.handle = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session Session) ReadWaitEvent() (handle windows.Handle) {
|
|
||||||
r0, _, _ := syscall.Syscall(procWintunGetReadWaitEvent.Addr(), 1, session.handle, 0, 0)
|
|
||||||
handle = windows.Handle(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session Session) ReceivePacket() (packet []byte, err error) {
|
|
||||||
var packetSize uint32
|
|
||||||
r0, _, e1 := syscall.Syscall(procWintunReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packetSize)), 0)
|
|
||||||
if r0 == 0 {
|
|
||||||
err = e1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session Session) ReleaseReceivePacket(packet []byte) {
|
|
||||||
syscall.Syscall(procWintunReleaseReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session Session) AllocateSendPacket(packetSize int) (packet []byte, err error) {
|
|
||||||
r0, _, e1 := syscall.Syscall(procWintunAllocateSendPacket.Addr(), 2, session.handle, uintptr(packetSize), 0)
|
|
||||||
if r0 == 0 {
|
|
||||||
err = e1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session Session) SendPacket(packet []byte) {
|
|
||||||
syscall.Syscall(procWintunSendPacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0)
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wintun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loggerLevel int
|
|
||||||
|
|
||||||
const (
|
|
||||||
logInfo loggerLevel = iota
|
|
||||||
logWarn
|
|
||||||
logErr
|
|
||||||
)
|
|
||||||
|
|
||||||
const AdapterNameMax = 128
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
handle uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
|
||||||
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
|
||||||
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
|
||||||
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
|
|
||||||
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
|
|
||||||
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
|
|
||||||
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
|
|
||||||
)
|
|
||||||
|
|
||||||
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
|
|
||||||
var lv log.LogLevel
|
|
||||||
switch level {
|
|
||||||
case logInfo:
|
|
||||||
lv = log.INFO
|
|
||||||
case logWarn:
|
|
||||||
lv = log.WARNING
|
|
||||||
case logErr:
|
|
||||||
lv = log.ERROR
|
|
||||||
default:
|
|
||||||
lv = log.INFO
|
|
||||||
}
|
|
||||||
log.PrintLog(lv, "[Wintun] %s", windows.UTF16PtrToString(msg))
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupLogger(dll *lazyDLL) {
|
|
||||||
var callback uintptr
|
|
||||||
if runtime.GOARCH == "386" {
|
|
||||||
callback = windows.NewCallback(func(level loggerLevel, timestampLow, timestampHigh uint32, msg *uint16) int {
|
|
||||||
return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
|
|
||||||
})
|
|
||||||
} else if runtime.GOARCH == "arm" {
|
|
||||||
callback = windows.NewCallback(func(level loggerLevel, _, timestampLow, timestampHigh uint32, msg *uint16) int {
|
|
||||||
return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
|
|
||||||
})
|
|
||||||
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
|
||||||
callback = windows.NewCallback(logMessage)
|
|
||||||
}
|
|
||||||
syscall.Syscall(dll.NewProc("WintunSetLogger").Addr(), 1, callback, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeAdapter(wintun *Adapter) {
|
|
||||||
syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAdapter creates a Wintun adapter. name is the cosmetic name of the adapter.
|
|
||||||
// tunnelType represents the type of adapter and should be "Wintun". requestedGUID is
|
|
||||||
// the GUID of the created network adapter, which then influences NLA generation
|
|
||||||
// deterministically. If it is set to nil, the GUID is chosen by the system at random,
|
|
||||||
// and hence a new NLA entry is created for each new adapter.
|
|
||||||
func CreateAdapter(name string, tunnelType string, requestedGUID *windows.GUID) (wintun *Adapter, err error) {
|
|
||||||
var name16 *uint16
|
|
||||||
name16, err = windows.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var tunnelType16 *uint16
|
|
||||||
tunnelType16, err = windows.UTF16PtrFromString(tunnelType)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall(procWintunCreateAdapter.Addr(), 3, uintptr(unsafe.Pointer(name16)), uintptr(unsafe.Pointer(tunnelType16)), uintptr(unsafe.Pointer(requestedGUID)))
|
|
||||||
if r0 == 0 {
|
|
||||||
err = e1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wintun = &Adapter{handle: r0}
|
|
||||||
runtime.SetFinalizer(wintun, closeAdapter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenAdapter opens an existing Wintun adapter by name.
|
|
||||||
func OpenAdapter(name string) (wintun *Adapter, err error) {
|
|
||||||
var name16 *uint16
|
|
||||||
name16, err = windows.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall(procWintunOpenAdapter.Addr(), 1, uintptr(unsafe.Pointer(name16)), 0, 0)
|
|
||||||
if r0 == 0 {
|
|
||||||
err = e1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wintun = &Adapter{handle: r0}
|
|
||||||
runtime.SetFinalizer(wintun, closeAdapter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes a Wintun adapter.
|
|
||||||
func (wintun *Adapter) Close() (err error) {
|
|
||||||
runtime.SetFinalizer(wintun, nil)
|
|
||||||
r1, _, e1 := syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uninstall removes the driver from the system if no drivers are currently in use.
|
|
||||||
func Uninstall() (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procWintunDeleteDriver.Addr(), 0, 0, 0, 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunningVersion returns the version of the loaded driver.
|
|
||||||
func RunningVersion() (version uint32, err error) {
|
|
||||||
r0, _, e1 := syscall.Syscall(procWintunGetRunningDriverVersion.Addr(), 0, 0, 0, 0)
|
|
||||||
version = uint32(r0)
|
|
||||||
if version == 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// LUID returns the LUID of the adapter.
|
|
||||||
func (wintun *Adapter) LUID() (luid uint64) {
|
|
||||||
syscall.Syscall(procWintunGetAdapterLUID.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&luid)), 0)
|
|
||||||
return
|
|
||||||
}
|
|
32
listener/tun/device/device.go
Normal file
32
listener/tun/device/device.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device is the interface that implemented by network layer devices (e.g. tun),
|
||||||
|
// and easy to use as stack.LinkEndpoint.
|
||||||
|
type Device interface {
|
||||||
|
stack.LinkEndpoint
|
||||||
|
|
||||||
|
// Name returns the current name of the device.
|
||||||
|
Name() string
|
||||||
|
|
||||||
|
// Type returns the driver type of the device.
|
||||||
|
Type() string
|
||||||
|
|
||||||
|
// Read packets from tun device
|
||||||
|
Read(packet []byte) (int, error)
|
||||||
|
|
||||||
|
// Write packets to tun device
|
||||||
|
Write(packet []byte) (int, error)
|
||||||
|
|
||||||
|
// Close stops and closes the device.
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// UseEndpoint work for gVisor stack
|
||||||
|
UseEndpoint() error
|
||||||
|
|
||||||
|
// UseIOBased work for other ip stack
|
||||||
|
UseIOBased() error
|
||||||
|
}
|
5
listener/tun/device/fdbased/fd.go
Normal file
5
listener/tun/device/fdbased/fd.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package fdbased
|
||||||
|
|
||||||
|
const Driver = "fd"
|
||||||
|
|
||||||
|
const defaultMTU = 1500
|
60
listener/tun/device/fdbased/fd_unix.go
Normal file
60
listener/tun/device/fdbased/fd_unix.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package fdbased
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FD struct {
|
||||||
|
stack.LinkEndpoint
|
||||||
|
|
||||||
|
fd int
|
||||||
|
mtu uint32
|
||||||
|
|
||||||
|
file *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(name string, mtu uint32) (device.Device, error) {
|
||||||
|
fd, err := strconv.Atoi(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot open fd: %s", name)
|
||||||
|
}
|
||||||
|
if mtu == 0 {
|
||||||
|
mtu = defaultMTU
|
||||||
|
}
|
||||||
|
return open(fd, mtu)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Type() string {
|
||||||
|
return Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Name() string {
|
||||||
|
return strconv.Itoa(f.fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Close() error {
|
||||||
|
err := unix.Close(f.fd)
|
||||||
|
if f.file != nil {
|
||||||
|
_ = f.file.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) UseEndpoint() error {
|
||||||
|
return f.useEndpoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) UseIOBased() error {
|
||||||
|
return f.useIOBased()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ device.Device = (*FD)(nil)
|
11
listener/tun/device/fdbased/fd_windows.go
Normal file
11
listener/tun/device/fdbased/fd_windows.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package fdbased
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Open(name string, mtu uint32) (device.Device, error) {
|
||||||
|
return nil, errors.New("not supported")
|
||||||
|
}
|
56
listener/tun/device/fdbased/open_linux.go
Normal file
56
listener/tun/device/fdbased/open_linux.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package fdbased
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open(fd int, mtu uint32) (device.Device, error) {
|
||||||
|
f := &FD{fd: fd, mtu: mtu}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) useEndpoint() error {
|
||||||
|
ep, err := fdbased.New(&fdbased.Options{
|
||||||
|
FDs: []int{f.fd},
|
||||||
|
MTU: f.mtu,
|
||||||
|
// TUN only, ignore ethernet header.
|
||||||
|
EthernetHeader: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create endpoint: %w", err)
|
||||||
|
}
|
||||||
|
f.LinkEndpoint = ep
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) useIOBased() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Read(packet []byte) (int, error) {
|
||||||
|
n, gvErr := rawfile.BlockingRead(f.fd, packet)
|
||||||
|
if gvErr != nil {
|
||||||
|
return 0, fmt.Errorf("read error: %s", gvErr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Write(packet []byte) (int, error) {
|
||||||
|
n := len(packet)
|
||||||
|
if n == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gvErr := rawfile.NonBlockingWrite(f.fd, packet)
|
||||||
|
if gvErr != nil {
|
||||||
|
return 0, fmt.Errorf("write error: %s", gvErr.String())
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
42
listener/tun/device/fdbased/open_others.go
Normal file
42
listener/tun/device/fdbased/open_others.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//go:build !linux && !windows
|
||||||
|
|
||||||
|
package fdbased
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open(fd int, mtu uint32) (device.Device, error) {
|
||||||
|
f := &FD{fd: fd, mtu: mtu}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) useEndpoint() error {
|
||||||
|
ep, err := iobased.New(os.NewFile(uintptr(f.fd), f.Name()), f.mtu, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create endpoint: %w", err)
|
||||||
|
}
|
||||||
|
f.LinkEndpoint = ep
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) useIOBased() error {
|
||||||
|
f.file = os.NewFile(uintptr(f.fd), f.Name())
|
||||||
|
if f.file == nil {
|
||||||
|
return fmt.Errorf("create IOBased failed, can not open file: %s", f.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Read(packet []byte) (int, error) {
|
||||||
|
return f.file.Read(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FD) Write(packet []byte) (int, error) {
|
||||||
|
return f.file.Write(packet)
|
||||||
|
}
|
134
listener/tun/device/iobased/endpoint.go
Normal file
134
listener/tun/device/iobased/endpoint.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Package iobased provides the implementation of io.ReadWriter
|
||||||
|
// based data-link layer endpoints.
|
||||||
|
package iobased
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Queue length for outbound packet, arriving for read. Overflow
|
||||||
|
// causes packet drops.
|
||||||
|
defaultOutQueueLen = 1 << 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint implements the interface of stack.LinkEndpoint from io.ReadWriter.
|
||||||
|
type Endpoint struct {
|
||||||
|
*channel.Endpoint
|
||||||
|
|
||||||
|
// rw is the io.ReadWriter for reading and writing packets.
|
||||||
|
rw io.ReadWriter
|
||||||
|
|
||||||
|
// mtu (maximum transmission unit) is the maximum size of a packet.
|
||||||
|
mtu uint32
|
||||||
|
|
||||||
|
// offset can be useful when perform TUN device I/O with TUN_PI enabled.
|
||||||
|
offset int
|
||||||
|
|
||||||
|
// once is used to perform the init action once when attaching.
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns stack.LinkEndpoint(.*Endpoint) and error.
|
||||||
|
func New(rw io.ReadWriter, mtu uint32, offset int) (*Endpoint, error) {
|
||||||
|
if mtu == 0 {
|
||||||
|
return nil, errors.New("MTU size is zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rw == nil {
|
||||||
|
return nil, errors.New("RW interface is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset < 0 {
|
||||||
|
return nil, errors.New("offset must be non-negative")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Endpoint{
|
||||||
|
Endpoint: channel.New(defaultOutQueueLen, mtu, ""),
|
||||||
|
rw: rw,
|
||||||
|
mtu: mtu,
|
||||||
|
offset: offset,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Endpoint) Close() {
|
||||||
|
e.Endpoint.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach launches the goroutine that reads packets from io.Reader and
|
||||||
|
// dispatches them via the provided dispatcher.
|
||||||
|
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||||
|
e.once.Do(func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go e.dispatchLoop(cancel)
|
||||||
|
go e.outboundLoop(ctx)
|
||||||
|
})
|
||||||
|
e.Endpoint.Attach(dispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatchLoop dispatches packets to upper layer.
|
||||||
|
func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
||||||
|
// Call cancel() to ensure (*Endpoint).outboundLoop(context.Context) exits
|
||||||
|
// gracefully after (*Endpoint).dispatchLoop(context.CancelFunc) returns.
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for {
|
||||||
|
data := make([]byte, int(e.mtu))
|
||||||
|
|
||||||
|
n, err := e.rw.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.IsAttached() {
|
||||||
|
continue /* unattached, drop packet */
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
Data: buffer.View(data[:n]).ToVectorisedView(),
|
||||||
|
})
|
||||||
|
|
||||||
|
switch header.IPVersion(data) {
|
||||||
|
case header.IPv4Version:
|
||||||
|
e.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
||||||
|
case header.IPv6Version:
|
||||||
|
e.InjectInbound(header.IPv6ProtocolNumber, pkt)
|
||||||
|
}
|
||||||
|
pkt.DecRef() /* release */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outboundLoop reads outbound packets from channel, and then it calls
|
||||||
|
// writePacket to send those packets back to lower layer.
|
||||||
|
func (e *Endpoint) outboundLoop(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
pkt := e.ReadContext(ctx)
|
||||||
|
if pkt == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e.writePacket(pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePacket writes outbound packets to the io.Writer.
|
||||||
|
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||||
|
defer pkt.DecRef()
|
||||||
|
|
||||||
|
size := pkt.Size()
|
||||||
|
views := pkt.Views()
|
||||||
|
|
||||||
|
vView := buffer.NewVectorisedView(size, views)
|
||||||
|
if _, err := e.rw.Write(vView.ToView()); err != nil {
|
||||||
|
return &tcpip.ErrInvalidEndpointState{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
14
listener/tun/device/tun/tun.go
Normal file
14
listener/tun/device/tun/tun.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Package tun provides TUN which implemented device.Device interface.
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Driver = "tun"
|
||||||
|
|
||||||
|
func (t *TUN) Type() string {
|
||||||
|
return Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ device.Device = (*TUN)(nil)
|
145
listener/tun/device/tun/tun_netstack.go
Normal file
145
listener/tun/device/tun/tun_netstack.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TUN struct {
|
||||||
|
stack.LinkEndpoint
|
||||||
|
|
||||||
|
fd int
|
||||||
|
mtu uint32
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(name string, mtu uint32) (device.Device, error) {
|
||||||
|
t := &TUN{name: name, mtu: mtu}
|
||||||
|
|
||||||
|
if len(t.name) >= unix.IFNAMSIZ {
|
||||||
|
return nil, fmt.Errorf("interface name too long: %s", t.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := tun.Open(t.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create tun: %w", err)
|
||||||
|
}
|
||||||
|
t.fd = fd
|
||||||
|
|
||||||
|
if t.mtu > 0 {
|
||||||
|
if err := setMTU(t.name, t.mtu); err != nil {
|
||||||
|
return nil, fmt.Errorf("set mtu: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mtu, err := rawfile.GetMTU(t.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get mtu: %w", err)
|
||||||
|
}
|
||||||
|
t.mtu = _mtu
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Name() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Read(packet []byte) (int, error) {
|
||||||
|
n, gvErr := rawfile.BlockingRead(t.fd, packet)
|
||||||
|
if gvErr != nil {
|
||||||
|
return 0, fmt.Errorf("read error: %s", gvErr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Write(packet []byte) (int, error) {
|
||||||
|
n := len(packet)
|
||||||
|
if n == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gvErr := rawfile.NonBlockingWrite(t.fd, packet)
|
||||||
|
if gvErr != nil {
|
||||||
|
return 0, fmt.Errorf("write error: %s", gvErr.String())
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Close() error {
|
||||||
|
return unix.Close(t.fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) UseEndpoint() error {
|
||||||
|
ep, err := fdbased.New(&fdbased.Options{
|
||||||
|
FDs: []int{t.fd},
|
||||||
|
MTU: t.mtu,
|
||||||
|
// TUN only, ignore ethernet header.
|
||||||
|
EthernetHeader: false,
|
||||||
|
// SYS_READV support only for TUN fd.
|
||||||
|
PacketDispatchMode: fdbased.Readv,
|
||||||
|
// TAP/TUN fd's are not sockets and using the WritePackets calls results
|
||||||
|
// in errors as it always defaults to using SendMMsg which is not supported
|
||||||
|
// for tap/tun device fds.
|
||||||
|
//
|
||||||
|
// This CL changes WritePackets to gracefully degrade to using writev instead
|
||||||
|
// of sendmmsg if the underlying fd is not a socket.
|
||||||
|
//
|
||||||
|
// Fixed: https://github.com/google/gvisor/commit/f33d034fecd7723a1e560ccc62aeeba328454fd0
|
||||||
|
MaxSyscallHeaderBytes: 0x00,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create endpoint: %w", err)
|
||||||
|
}
|
||||||
|
t.LinkEndpoint = ep
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) UseIOBased() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref: wireguard tun/tun_linux.go setMTU.
|
||||||
|
func setMTU(name string, n uint32) error {
|
||||||
|
// open datagram socket
|
||||||
|
fd, err := unix.Socket(
|
||||||
|
unix.AF_INET,
|
||||||
|
unix.SOCK_DGRAM,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer unix.Close(fd)
|
||||||
|
|
||||||
|
const ifReqSize = unix.IFNAMSIZ + 64
|
||||||
|
|
||||||
|
// do ioctl call
|
||||||
|
var ifr [ifReqSize]byte
|
||||||
|
copy(ifr[:], name)
|
||||||
|
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = n
|
||||||
|
_, _, errno := unix.Syscall(
|
||||||
|
unix.SYS_IOCTL,
|
||||||
|
uintptr(fd),
|
||||||
|
uintptr(unix.SIOCSIFMTU),
|
||||||
|
uintptr(unsafe.Pointer(&ifr[0])),
|
||||||
|
)
|
||||||
|
|
||||||
|
if errno != 0 {
|
||||||
|
return fmt.Errorf("failed to set MTU: %w", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
17
listener/tun/device/tun/tun_windows.go
Normal file
17
listener/tun/device/tun/tun_windows.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
guid, _ := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}")
|
||||||
|
|
||||||
|
tun.WintunTunnelType = "Clash"
|
||||||
|
tun.WintunStaticRequestedGUID = &guid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) LUID() uint64 {
|
||||||
|
return t.nt.LUID()
|
||||||
|
}
|
117
listener/tun/device/tun/tun_wireguard.go
Normal file
117
listener/tun/device/tun/tun_wireguard.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TUN struct {
|
||||||
|
*iobased.Endpoint
|
||||||
|
|
||||||
|
nt *tun.NativeTun
|
||||||
|
mtu uint32
|
||||||
|
name string
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(name string, mtu uint32) (_ device.Device, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("open tun: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
offset = 4 /* 4 bytes TUN_PI */
|
||||||
|
defaultMTU = 1500
|
||||||
|
)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
offset = 0
|
||||||
|
defaultMTU = 0 /* auto */
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &TUN{
|
||||||
|
name: name,
|
||||||
|
mtu: mtu,
|
||||||
|
offset: offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
forcedMTU := defaultMTU
|
||||||
|
if t.mtu > 0 {
|
||||||
|
forcedMTU = int(t.mtu)
|
||||||
|
}
|
||||||
|
|
||||||
|
nt, err := tun.CreateTUN(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create tun: %w", err)
|
||||||
|
}
|
||||||
|
t.nt = nt.(*tun.NativeTun)
|
||||||
|
|
||||||
|
tunMTU, err := nt.MTU()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get mtu: %w", err)
|
||||||
|
}
|
||||||
|
t.mtu = uint32(tunMTU)
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Read(packet []byte) (int, error) {
|
||||||
|
if t.offset == 0 {
|
||||||
|
return t.nt.Read(packet, t.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
buff := pool.Get(t.offset + cap(packet))
|
||||||
|
defer pool.Put(buff)
|
||||||
|
|
||||||
|
n, err := t.nt.Read(buff, t.offset)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = buff[:t.offset]
|
||||||
|
|
||||||
|
copy(packet, buff[t.offset:t.offset+n])
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Write(packet []byte) (int, error) {
|
||||||
|
if t.offset == 0 {
|
||||||
|
return t.nt.Write(packet, t.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = append(make([]byte, t.offset), packet...)
|
||||||
|
|
||||||
|
return t.nt.Write(packet, t.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Close() error {
|
||||||
|
return t.nt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) Name() string {
|
||||||
|
name, _ := t.nt.Name()
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) UseEndpoint() error {
|
||||||
|
ep, err := iobased.New(t, t.mtu, t.offset)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create endpoint: %w", err)
|
||||||
|
}
|
||||||
|
t.Endpoint = ep
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TUN) UseIOBased() error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,10 +1,24 @@
|
|||||||
package commons
|
package commons
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DefaultDnsReadTimeout = time.Second * 10
|
||||||
|
|
||||||
|
func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool {
|
||||||
|
for _, addrPort := range dnsAdds {
|
||||||
|
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
||||||
msg := &D.Msg{}
|
msg := &D.Msg{}
|
||||||
if err := msg.Unpack(payload); err != nil {
|
if err := msg.Unpack(payload); err != nil {
|
||||||
|
17
listener/tun/ipstack/commons/router.go
Normal file
17
listener/tun/ipstack/commons/router.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Routes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
|
||||||
|
|
||||||
|
func IPv4MaskString(bits int) string {
|
||||||
|
m := net.CIDRMask(bits, 32)
|
||||||
|
if len(m) != 4 {
|
||||||
|
panic("ipv4Mask: len must be 4 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
||||||
|
}
|
61
listener/tun/ipstack/commons/router_darwin.go
Normal file
61
listener/tun/ipstack/commons/router_darwin.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cmd"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAutoDetectInterface() (string, error) {
|
||||||
|
return cmd.ExecCmd("bash -c netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||||
|
if !addr.Addr().Is4() {
|
||||||
|
return fmt.Errorf("supported ipv4 only")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
interfaceName = dev.Name()
|
||||||
|
ip = addr.Masked().Addr().Next()
|
||||||
|
gw = ip
|
||||||
|
netmask = IPv4MaskString(addr.Bits())
|
||||||
|
)
|
||||||
|
|
||||||
|
cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw)
|
||||||
|
|
||||||
|
_, err := cmd.ExecCmd(cmdStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// _, err = cmd.ExecCmd(fmt.Sprintf("ipconfig set %s automatic-v6", interfaceName))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
if autoRoute {
|
||||||
|
err = configInterfaceRouting(interfaceName, addr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
||||||
|
routes := append(Routes, addr.String())
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
if err := execRouterCmd("add", "-inet", route, interfaceName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return execRouterCmd("add", "-inet6", "2000::/3", interfaceName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execRouterCmd(action, inet, route string, interfaceName string) error {
|
||||||
|
_, err := cmd.ExecCmd(fmt.Sprintf("route %s %s %s -interface %s", action, inet, route, interfaceName))
|
||||||
|
return err
|
||||||
|
}
|
52
listener/tun/ipstack/commons/router_linux.go
Normal file
52
listener/tun/ipstack/commons/router_linux.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/cmd"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAutoDetectInterface() (string, error) {
|
||||||
|
return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||||
|
var (
|
||||||
|
interfaceName = dev.Name()
|
||||||
|
ip = addr.Masked().Addr().Next()
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := cmd.ExecCmd(fmt.Sprintf("ip addr add %s dev %s", ip.String(), interfaceName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cmd.ExecCmd(fmt.Sprintf("ip link set %s up", interfaceName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoRoute {
|
||||||
|
err = configInterfaceRouting(interfaceName, addr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
||||||
|
linkIP := addr.Masked().Addr().Next()
|
||||||
|
for _, route := range Routes {
|
||||||
|
if err := execRouterCmd("add", route, interfaceName, linkIP.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execRouterCmd(action, route string, interfaceName string, linkIP string) error {
|
||||||
|
cmdStr := fmt.Sprintf("ip route %s %s dev %s proto kernel scope link src %s", action, route, interfaceName, linkIP)
|
||||||
|
|
||||||
|
_, err := cmd.ExecCmd(cmdStr)
|
||||||
|
return err
|
||||||
|
}
|
19
listener/tun/ipstack/commons/router_others.go
Normal file
19
listener/tun/ipstack/commons/router_others.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//go:build !darwin && !linux && !windows
|
||||||
|
|
||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAutoDetectInterface() (string, error) {
|
||||||
|
return "", fmt.Errorf("can not auto detect interface-name on this OS: %s, you must be detecting interface-name by manual", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
||||||
|
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
||||||
|
}
|
261
listener/tun/ipstack/commons/router_windows.go
Normal file
261
listener/tun/ipstack/commons/router_windows.go
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.zx2c4.com/wireguard/windows/services"
|
||||||
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAutoDetectInterface() (string, error) {
|
||||||
|
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
||||||
|
if err == nil {
|
||||||
|
return ifname, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||||
|
retryOnFailure := services.StartedAtBoot()
|
||||||
|
tryTimes := 0
|
||||||
|
var err error
|
||||||
|
startOver:
|
||||||
|
if tryTimes > 0 {
|
||||||
|
log.Infoln("Retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
retryOnFailure = retryOnFailure && tryTimes < 15
|
||||||
|
}
|
||||||
|
tryTimes++
|
||||||
|
|
||||||
|
var (
|
||||||
|
luid = winipcfg.LUID(dev.(*tun.TUN).LUID())
|
||||||
|
ip = addr.Masked().Addr().Next()
|
||||||
|
addresses = []netip.Prefix{netip.PrefixFrom(ip, addr.Bits())}
|
||||||
|
|
||||||
|
family4 = winipcfg.AddressFamily(windows.AF_INET)
|
||||||
|
familyV6 = winipcfg.AddressFamily(windows.AF_INET6)
|
||||||
|
currentFamily = winipcfg.AddressFamily(windows.AF_INET6)
|
||||||
|
)
|
||||||
|
|
||||||
|
if addr.Addr().Is4() {
|
||||||
|
currentFamily = winipcfg.AddressFamily(windows.AF_INET)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = luid.FlushRoutes(windows.AF_INET6)
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = luid.FlushIPAddresses(windows.AF_INET6)
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = luid.FlushDNS(windows.AF_INET6)
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = luid.FlushDNS(windows.AF_INET)
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDefault4 := false
|
||||||
|
foundDefault6 := false
|
||||||
|
|
||||||
|
if autoRoute {
|
||||||
|
var (
|
||||||
|
allowedIPs []netip.Prefix
|
||||||
|
|
||||||
|
// add default
|
||||||
|
routeArr = []string{"0.0.0.0/0"}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, route := range routeArr {
|
||||||
|
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
|
||||||
|
}
|
||||||
|
|
||||||
|
estimatedRouteCount := len(allowedIPs)
|
||||||
|
routes := make(map[winipcfg.RouteData]bool, estimatedRouteCount)
|
||||||
|
|
||||||
|
for _, allowedip := range allowedIPs {
|
||||||
|
route := winipcfg.RouteData{
|
||||||
|
Destination: allowedip.Masked(),
|
||||||
|
Metric: 0,
|
||||||
|
}
|
||||||
|
if allowedip.Addr().Is4() {
|
||||||
|
if allowedip.Bits() == 0 {
|
||||||
|
foundDefault4 = true
|
||||||
|
}
|
||||||
|
route.NextHop = netip.IPv4Unspecified()
|
||||||
|
} else if allowedip.Addr().Is6() {
|
||||||
|
if allowedip.Bits() == 0 {
|
||||||
|
foundDefault6 = true
|
||||||
|
}
|
||||||
|
route.NextHop = netip.IPv6Unspecified()
|
||||||
|
}
|
||||||
|
routes[route] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
|
||||||
|
for route := range routes {
|
||||||
|
r := route
|
||||||
|
deduplicatedRoutes = append(deduplicatedRoutes, &r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add gateway
|
||||||
|
deduplicatedRoutes = append(deduplicatedRoutes, &winipcfg.RouteData{
|
||||||
|
Destination: addr.Masked(),
|
||||||
|
NextHop: addr.Masked().Addr().Next().Next(),
|
||||||
|
Metric: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
err = luid.SetRoutesForFamily(currentFamily, deduplicatedRoutes)
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("unable to set routes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
||||||
|
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
||||||
|
cleanupAddressesOnDisconnectedInterfaces(currentFamily, addresses)
|
||||||
|
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
||||||
|
}
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("unable to set ips: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipif *winipcfg.MibIPInterfaceRow
|
||||||
|
ipif, err = luid.IPInterface(family4)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ipif.ForwardingEnabled = true
|
||||||
|
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
||||||
|
ipif.DadTransmits = 0
|
||||||
|
ipif.ManagedAddressConfigurationSupported = false
|
||||||
|
ipif.OtherStatefulConfigurationSupported = false
|
||||||
|
if forceMTU > 0 {
|
||||||
|
ipif.NLMTU = uint32(forceMTU)
|
||||||
|
}
|
||||||
|
if foundDefault4 {
|
||||||
|
ipif.UseAutomaticMetric = false
|
||||||
|
ipif.Metric = 0
|
||||||
|
}
|
||||||
|
err = ipif.Set()
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("unable to set metric and MTU: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipif6 *winipcfg.MibIPInterfaceRow
|
||||||
|
ipif6, err = luid.IPInterface(familyV6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
||||||
|
ipif6.DadTransmits = 0
|
||||||
|
ipif6.ManagedAddressConfigurationSupported = false
|
||||||
|
ipif6.OtherStatefulConfigurationSupported = false
|
||||||
|
if forceMTU > 0 {
|
||||||
|
ipif6.NLMTU = uint32(forceMTU)
|
||||||
|
}
|
||||||
|
if foundDefault6 {
|
||||||
|
ipif6.UseAutomaticMetric = false
|
||||||
|
ipif6.Metric = 0
|
||||||
|
}
|
||||||
|
err = ipif6.Set()
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("unable to set v6 metric and MTU: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsAdds := []netip.Addr{netip.MustParseAddr("198.18.0.2")}
|
||||||
|
err = luid.SetDNS(family4, dnsAdds, nil)
|
||||||
|
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||||
|
goto startOver
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("unable to set DNS %s %s: %w", "198.18.0.2", "nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addrHash := make(map[netip.Addr]bool, len(addresses))
|
||||||
|
for i := range addresses {
|
||||||
|
addrHash[addresses[i].Addr()] = true
|
||||||
|
}
|
||||||
|
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if iface.OperStatus == winipcfg.IfOperStatusUp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
||||||
|
if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
|
||||||
|
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
||||||
|
log.Infoln("Cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
||||||
|
_ = iface.LUID.DeleteIPAddress(prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
||||||
|
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("get ethernet interface failure. %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var destination netip.Prefix
|
||||||
|
if family == windows.AF_INET {
|
||||||
|
destination = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||||
|
} else {
|
||||||
|
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if iface.OperStatus != winipcfg.IfOperStatusUp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ifname := iface.FriendlyName()
|
||||||
|
|
||||||
|
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
||||||
|
nextHop, _ := netip.AddrFromSlice(gatewayAddress.Address.IP())
|
||||||
|
|
||||||
|
if _, err = iface.LUID.Route(destination, nextHop); err == nil {
|
||||||
|
return ifname, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("ethernet interface not found")
|
||||||
|
}
|
24
listener/tun/ipstack/gvisor/adapter/adapter.go
Normal file
24
listener/tun/ipstack/gvisor/adapter/adapter.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TCPConn implements the net.Conn interface.
|
||||||
|
type TCPConn interface {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
// ID returns the transport endpoint id of TCPConn.
|
||||||
|
ID() *stack.TransportEndpointID
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPConn implements net.Conn and net.PacketConn.
|
||||||
|
type UDPConn interface {
|
||||||
|
net.Conn
|
||||||
|
net.PacketConn
|
||||||
|
|
||||||
|
// ID returns the transport endpoint id of UDPConn.
|
||||||
|
ID() *stack.TransportEndpointID
|
||||||
|
}
|
8
listener/tun/ipstack/gvisor/adapter/handler.go
Normal file
8
listener/tun/ipstack/gvisor/adapter/handler.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
// Handler is a TCP/UDP connection handler that implements
|
||||||
|
// HandleTCPConn and HandleUDPConn methods.
|
||||||
|
type Handler interface {
|
||||||
|
HandleTCPConn(TCPConn)
|
||||||
|
HandleUDPConn(UDPConn)
|
||||||
|
}
|
134
listener/tun/ipstack/gvisor/handler.go
Normal file
134
listener/tun/ipstack/gvisor/handler.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.Handler = (*GVHandler)(nil)
|
||||||
|
|
||||||
|
type GVHandler struct {
|
||||||
|
DNSAdds []netip.AddrPort
|
||||||
|
|
||||||
|
TCPIn chan<- C.ConnContext
|
||||||
|
UDPIn chan<- *inbound.PacketAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gh *GVHandler) HandleTCPConn(tunConn adapter.TCPConn) {
|
||||||
|
id := tunConn.ID()
|
||||||
|
|
||||||
|
rAddr := &net.UDPAddr{
|
||||||
|
IP: net.IP(id.LocalAddress),
|
||||||
|
Port: int(id.LocalPort),
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
addrIp, _ := netip.AddrFromSlice(rAddr.IP)
|
||||||
|
addrPort := netip.AddrPortFrom(addrIp, id.LocalPort)
|
||||||
|
|
||||||
|
if D.ShouldHijackDns(gh.DNSAdds, addrPort) {
|
||||||
|
go func() {
|
||||||
|
log.Debugln("[TUN] hijack dns tcp: %s", addrPort.String())
|
||||||
|
|
||||||
|
defer tunConn.Close()
|
||||||
|
|
||||||
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
|
||||||
|
for {
|
||||||
|
tunConn.SetReadDeadline(time.Now().Add(D.DefaultDnsReadTimeout))
|
||||||
|
|
||||||
|
length := uint16(0)
|
||||||
|
if err := binary.Read(tunConn, binary.BigEndian, &length); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(length) > len(buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := tunConn.Read(buf[:length])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := D.RelayDnsPacket(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = tunConn.Write(msg)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gh.TCPIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gh *GVHandler) HandleUDPConn(tunConn adapter.UDPConn) {
|
||||||
|
id := tunConn.ID()
|
||||||
|
|
||||||
|
rAddr := &net.UDPAddr{
|
||||||
|
IP: net.IP(id.LocalAddress),
|
||||||
|
Port: int(id.LocalPort),
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
addrIp, _ := netip.AddrFromSlice(rAddr.IP)
|
||||||
|
addrPort := netip.AddrPortFrom(addrIp, id.LocalPort)
|
||||||
|
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
|
|
||||||
|
n, addr, err := tunConn.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
pool.Put(buf)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := buf[:n]
|
||||||
|
|
||||||
|
if D.ShouldHijackDns(gh.DNSAdds, addrPort) {
|
||||||
|
go func() {
|
||||||
|
defer pool.Put(buf)
|
||||||
|
|
||||||
|
msg, err1 := D.RelayDnsPacket(payload)
|
||||||
|
if err1 != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = tunConn.WriteTo(msg, addr)
|
||||||
|
|
||||||
|
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
|
||||||
|
}()
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gvPacket := &packet{
|
||||||
|
pc: tunConn,
|
||||||
|
rAddr: addr,
|
||||||
|
payload: payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case gh.UDPIn <- inbound.NewPacket(target, gvPacket, C.TUN):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
24
listener/tun/ipstack/gvisor/icmp.go
Normal file
24
listener/tun/ipstack/gvisor/icmp.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withICMPHandler() Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
// Add default route table for IPv4 and IPv6.
|
||||||
|
// This will handle all incoming ICMP packets.
|
||||||
|
s.SetRouteTable([]tcpip.Route{
|
||||||
|
{
|
||||||
|
Destination: header.IPv4EmptySubnet,
|
||||||
|
NIC: s.nicID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: header.IPv6EmptySubnet,
|
||||||
|
NIC: s.nicID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
59
listener/tun/ipstack/gvisor/nic.go
Normal file
59
listener/tun/ipstack/gvisor/nic.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultNICID is the ID of default NIC used by DefaultStack.
|
||||||
|
defaultNICID tcpip.NICID = 0x01
|
||||||
|
|
||||||
|
// nicPromiscuousModeEnabled is the value used by stack to enable
|
||||||
|
// or disable NIC's promiscuous mode.
|
||||||
|
nicPromiscuousModeEnabled = true
|
||||||
|
|
||||||
|
// nicSpoofingEnabled is the value used by stack to enable or disable
|
||||||
|
// NIC's spoofing.
|
||||||
|
nicSpoofingEnabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// withCreatingNIC creates NIC for stack.
|
||||||
|
func withCreatingNIC(ep stack.LinkEndpoint) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
if err := s.CreateNICWithOptions(s.nicID, ep,
|
||||||
|
stack.NICOptions{
|
||||||
|
Disabled: false,
|
||||||
|
// If no queueing discipline was specified
|
||||||
|
// provide a stub implementation that just
|
||||||
|
// delegates to the lower link endpoint.
|
||||||
|
QDisc: nil,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("create NIC: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// withPromiscuousMode sets promiscuous mode in the given NIC.
|
||||||
|
func withPromiscuousMode(v bool) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
if err := s.SetPromiscuousMode(s.nicID, v); err != nil {
|
||||||
|
return fmt.Errorf("set promiscuous mode: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// withSpoofing sets address spoofing in the given NIC, allowing
|
||||||
|
// endpoints to bind to any address in the NIC.
|
||||||
|
func withSpoofing(v bool) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
if err := s.SetSpoofing(s.nicID, v); err != nil {
|
||||||
|
return fmt.Errorf("set spoofing: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
223
listener/tun/ipstack/gvisor/opts.go
Normal file
223
listener/tun/ipstack/gvisor/opts.go
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultTimeToLive specifies the default TTL used by stack.
|
||||||
|
defaultTimeToLive uint8 = 64
|
||||||
|
|
||||||
|
// ipForwardingEnabled is the value used by stack to enable packet
|
||||||
|
// forwarding between NICs.
|
||||||
|
ipForwardingEnabled = true
|
||||||
|
|
||||||
|
// icmpBurst is the default number of ICMP messages that can be sent in
|
||||||
|
// a single burst.
|
||||||
|
icmpBurst = 50
|
||||||
|
|
||||||
|
// icmpLimit is the default maximum number of ICMP messages permitted
|
||||||
|
// by this rate limiter.
|
||||||
|
icmpLimit rate.Limit = 1000
|
||||||
|
|
||||||
|
// tcpCongestionControl is the congestion control algorithm used by
|
||||||
|
// stack. ccReno is the default option in gVisor stack.
|
||||||
|
tcpCongestionControlAlgorithm = "reno" // "reno" or "cubic"
|
||||||
|
|
||||||
|
// tcpDelayEnabled is the value used by stack to enable or disable
|
||||||
|
// tcp delay option. Disable Nagle's algorithm here by default.
|
||||||
|
tcpDelayEnabled = false
|
||||||
|
|
||||||
|
// tcpModerateReceiveBufferEnabled is the value used by stack to
|
||||||
|
// enable or disable tcp receive buffer auto-tuning option.
|
||||||
|
tcpModerateReceiveBufferEnabled = true
|
||||||
|
|
||||||
|
// tcpSACKEnabled is the value used by stack to enable or disable
|
||||||
|
// tcp selective ACK.
|
||||||
|
tcpSACKEnabled = true
|
||||||
|
|
||||||
|
// tcpRecovery is the loss detection algorithm used by TCP.
|
||||||
|
tcpRecovery = tcpip.TCPRACKLossDetection
|
||||||
|
|
||||||
|
// tcpMinBufferSize is the smallest size of a send/recv buffer.
|
||||||
|
tcpMinBufferSize = tcp.MinBufferSize // 4 KiB
|
||||||
|
|
||||||
|
// tcpMaxBufferSize is the maximum permitted size of a send/recv buffer.
|
||||||
|
tcpMaxBufferSize = tcp.MaxBufferSize // 4 MiB
|
||||||
|
|
||||||
|
// tcpDefaultBufferSize is the default size of the send/recv buffer for
|
||||||
|
// a transport endpoint.
|
||||||
|
tcpDefaultBufferSize = 212 << 10 // 212 KiB
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(*gvStack) error
|
||||||
|
|
||||||
|
// WithDefault sets all default values for stack.
|
||||||
|
func WithDefault() Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
opts := []Option{
|
||||||
|
WithDefaultTTL(defaultTimeToLive),
|
||||||
|
WithForwarding(ipForwardingEnabled),
|
||||||
|
|
||||||
|
// Config default stack ICMP settings.
|
||||||
|
WithICMPBurst(icmpBurst), WithICMPLimit(icmpLimit),
|
||||||
|
|
||||||
|
// We expect no packet loss, therefore we can bump buffers.
|
||||||
|
// Too large buffers thrash cache, so there is little point
|
||||||
|
// in too large buffers.
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
|
||||||
|
WithTCPBufferSizeRange(tcpMinBufferSize, tcpDefaultBufferSize, tcpMaxBufferSize),
|
||||||
|
|
||||||
|
WithTCPCongestionControl(tcpCongestionControlAlgorithm),
|
||||||
|
WithTCPDelay(tcpDelayEnabled),
|
||||||
|
|
||||||
|
// Receive Buffer Auto-Tuning Option, see:
|
||||||
|
// https://github.com/google/gvisor/issues/1666
|
||||||
|
WithTCPModerateReceiveBuffer(tcpModerateReceiveBufferEnabled),
|
||||||
|
|
||||||
|
// TCP selective ACK Option, see:
|
||||||
|
// https://tools.ietf.org/html/rfc2018
|
||||||
|
WithTCPSACKEnabled(tcpSACKEnabled),
|
||||||
|
|
||||||
|
// TCPRACKLossDetection: indicates RACK is used for loss detection and
|
||||||
|
// recovery.
|
||||||
|
//
|
||||||
|
// TCPRACKStaticReoWnd: indicates the reordering window should not be
|
||||||
|
// adjusted when DSACK is received.
|
||||||
|
//
|
||||||
|
// TCPRACKNoDupTh: indicates RACK should not consider the classic three
|
||||||
|
// duplicate acknowledgements rule to mark the segments as lost. This
|
||||||
|
// is used when reordering is not detected.
|
||||||
|
WithTCPRecovery(tcpRecovery),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultTTL sets the default TTL used by stack.
|
||||||
|
func WithDefaultTTL(ttl uint8) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
opt := tcpip.DefaultTTLOption(ttl)
|
||||||
|
if err := s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &opt); err != nil {
|
||||||
|
return fmt.Errorf("set ipv4 default TTL: %s", err)
|
||||||
|
}
|
||||||
|
if err := s.SetNetworkProtocolOption(ipv6.ProtocolNumber, &opt); err != nil {
|
||||||
|
return fmt.Errorf("set ipv6 default TTL: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithForwarding sets packet forwarding between NICs for IPv4 & IPv6.
|
||||||
|
func WithForwarding(v bool) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, v); err != nil {
|
||||||
|
return fmt.Errorf("set ipv4 forwarding: %s", err)
|
||||||
|
}
|
||||||
|
if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, v); err != nil {
|
||||||
|
return fmt.Errorf("set ipv6 forwarding: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithICMPBurst sets the number of ICMP messages that can be sent
|
||||||
|
// in a single burst.
|
||||||
|
func WithICMPBurst(burst int) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
s.SetICMPBurst(burst)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithICMPLimit sets the maximum number of ICMP messages permitted
|
||||||
|
// by rate limiter.
|
||||||
|
func WithICMPLimit(limit rate.Limit) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
s.SetICMPLimit(limit)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTCPBufferSizeRange sets the receive and send buffer size range for TCP.
|
||||||
|
func WithTCPBufferSizeRange(a, b, c int) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c}
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
|
||||||
|
return fmt.Errorf("set TCP receive buffer size range: %s", err)
|
||||||
|
}
|
||||||
|
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c}
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
|
||||||
|
return fmt.Errorf("set TCP send buffer size range: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTCPCongestionControl sets the current congestion control algorithm.
|
||||||
|
func WithTCPCongestionControl(cc string) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
opt := tcpip.CongestionControlOption(cc)
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||||
|
return fmt.Errorf("set TCP congestion control algorithm: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTCPDelay enables or disables Nagle's algorithm in TCP.
|
||||||
|
func WithTCPDelay(v bool) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
opt := tcpip.TCPDelayEnabled(v)
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||||
|
return fmt.Errorf("set TCP delay: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTCPModerateReceiveBuffer sets receive buffer moderation for TCP.
|
||||||
|
func WithTCPModerateReceiveBuffer(v bool) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
opt := tcpip.TCPModerateReceiveBufferOption(v)
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||||
|
return fmt.Errorf("set TCP moderate receive buffer: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTCPSACKEnabled sets the SACK option for TCP.
|
||||||
|
func WithTCPSACKEnabled(v bool) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
opt := tcpip.TCPSACKEnabled(v)
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||||
|
return fmt.Errorf("set TCP SACK: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTCPRecovery sets the recovery option for TCP.
|
||||||
|
func WithTCPRecovery(v tcpip.TCPRecovery) Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &v); err != nil {
|
||||||
|
return fmt.Errorf("set TCP Recovery: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
96
listener/tun/ipstack/gvisor/stack.go
Normal file
96
listener/tun/ipstack/gvisor/stack.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Package gvisor provides a thin wrapper around a gVisor's stack.
|
||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gvStack struct {
|
||||||
|
*stack.Stack
|
||||||
|
device device.Device
|
||||||
|
|
||||||
|
handler adapter.Handler
|
||||||
|
nicID tcpip.NICID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *gvStack) Close() error {
|
||||||
|
if s.Stack != nil {
|
||||||
|
s.Stack.Close()
|
||||||
|
}
|
||||||
|
if s.device != nil {
|
||||||
|
_ = s.device.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new *gvStack with given options.
|
||||||
|
func New(device device.Device, handler adapter.Handler, opts ...Option) (ipstack.Stack, error) {
|
||||||
|
s := &gvStack{
|
||||||
|
Stack: stack.New(stack.Options{
|
||||||
|
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||||
|
ipv4.NewProtocol,
|
||||||
|
ipv6.NewProtocol,
|
||||||
|
},
|
||||||
|
TransportProtocols: []stack.TransportProtocolFactory{
|
||||||
|
tcp.NewProtocol,
|
||||||
|
udp.NewProtocol,
|
||||||
|
icmp.NewProtocol4,
|
||||||
|
icmp.NewProtocol6,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
device: device,
|
||||||
|
handler: handler,
|
||||||
|
nicID: defaultNICID,
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts,
|
||||||
|
// Important: We must initiate transport protocol handlers
|
||||||
|
// before creating NIC, otherwise NIC would dispatch packets
|
||||||
|
// to stack and cause race condition.
|
||||||
|
withICMPHandler(), withTCPHandler(), withUDPHandler(),
|
||||||
|
|
||||||
|
// Create stack NIC and then bind link endpoint.
|
||||||
|
withCreatingNIC(device.(stack.LinkEndpoint)),
|
||||||
|
|
||||||
|
// In the past we did s.AddAddressRange to assign 0.0.0.0/0
|
||||||
|
// onto the interface. We need that to be able to terminate
|
||||||
|
// all the incoming connections - to any ip. AddressRange API
|
||||||
|
// has been removed and the suggested workaround is to use
|
||||||
|
// Promiscuous mode. https://github.com/google/gvisor/issues/3876
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
|
||||||
|
withPromiscuousMode(nicPromiscuousModeEnabled),
|
||||||
|
|
||||||
|
// Enable spoofing if a stack may send packets from unowned addresses.
|
||||||
|
// This change required changes to some netgophers since previously,
|
||||||
|
// promiscuous mode was enough to let the netstack respond to all
|
||||||
|
// incoming packets regardless of the packet's destination address. Now
|
||||||
|
// that a stack.Route is not held for each incoming packet, finding a route
|
||||||
|
// may fail with local addresses we don't own but accepted packets for
|
||||||
|
// while in promiscuous mode. Since we also want to be able to send from
|
||||||
|
// any address (in response the received promiscuous mode packets), we need
|
||||||
|
// to enable spoofing.
|
||||||
|
//
|
||||||
|
// Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127
|
||||||
|
withSpoofing(nicSpoofingEnabled),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
91
listener/tun/ipstack/gvisor/tcp.go
Normal file
91
listener/tun/ipstack/gvisor/tcp.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
"gvisor.dev/gvisor/pkg/waiter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultWndSize if set to zero, the default
|
||||||
|
// receive window buffer size is used instead.
|
||||||
|
defaultWndSize = 0
|
||||||
|
|
||||||
|
// maxConnAttempts specifies the maximum number
|
||||||
|
// of in-flight tcp connection attempts.
|
||||||
|
maxConnAttempts = 2 << 10
|
||||||
|
|
||||||
|
// tcpKeepaliveCount is the maximum number of
|
||||||
|
// TCP keep-alive probes to send before giving up
|
||||||
|
// and killing the connection if no response is
|
||||||
|
// obtained from the other end.
|
||||||
|
tcpKeepaliveCount = 9
|
||||||
|
|
||||||
|
// tcpKeepaliveIdle specifies the time a connection
|
||||||
|
// must remain idle before the first TCP keepalive
|
||||||
|
// packet is sent. Once this time is reached,
|
||||||
|
// tcpKeepaliveInterval option is used instead.
|
||||||
|
tcpKeepaliveIdle = 60 * time.Second
|
||||||
|
|
||||||
|
// tcpKeepaliveInterval specifies the interval
|
||||||
|
// time between sending TCP keepalive packets.
|
||||||
|
tcpKeepaliveInterval = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func withTCPHandler() Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
tcpForwarder := tcp.NewForwarder(s.Stack, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
|
||||||
|
var wq waiter.Queue
|
||||||
|
ep, err := r.CreateEndpoint(&wq)
|
||||||
|
if err != nil {
|
||||||
|
// RST: prevent potential half-open TCP connection leak.
|
||||||
|
r.Complete(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Complete(false)
|
||||||
|
|
||||||
|
setKeepalive(ep)
|
||||||
|
|
||||||
|
conn := &tcpConn{
|
||||||
|
TCPConn: gonet.NewTCPConn(&wq, ep),
|
||||||
|
id: r.ID(),
|
||||||
|
}
|
||||||
|
s.handler.HandleTCPConn(conn)
|
||||||
|
})
|
||||||
|
s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setKeepalive(ep tcpip.Endpoint) error {
|
||||||
|
ep.SocketOptions().SetKeepAlive(true)
|
||||||
|
|
||||||
|
idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
|
||||||
|
if err := ep.SetSockOpt(&idle); err != nil {
|
||||||
|
return fmt.Errorf("set keepalive idle: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
|
||||||
|
if err := ep.SetSockOpt(&interval); err != nil {
|
||||||
|
return fmt.Errorf("set keepalive interval: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil {
|
||||||
|
return fmt.Errorf("set keepalive count: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcpConn struct {
|
||||||
|
*gonet.TCPConn
|
||||||
|
id stack.TransportEndpointID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tcpConn) ID() *stack.TransportEndpointID {
|
||||||
|
return &c.id
|
||||||
|
}
|
@ -1,280 +0,0 @@
|
|||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
"github.com/Dreamacro/clash/config"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/dns"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/dev"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
||||||
"gvisor.dev/gvisor/pkg/waiter"
|
|
||||||
)
|
|
||||||
|
|
||||||
const nicID tcpip.NICID = 1
|
|
||||||
|
|
||||||
type gvisorAdapter struct {
|
|
||||||
device dev.TunDevice
|
|
||||||
ipstack *stack.Stack
|
|
||||||
dnsServer *DNSServer
|
|
||||||
udpIn chan<- *inbound.PacketAdapter
|
|
||||||
|
|
||||||
stackName string
|
|
||||||
autoRoute bool
|
|
||||||
linkCache *channel.Endpoint
|
|
||||||
wg sync.WaitGroup // wait for goroutines to stop
|
|
||||||
|
|
||||||
writeHandle *channel.NotificationHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
// GvisorAdapter create GvisorAdapter
|
|
||||||
func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
|
|
||||||
ipstack := stack.New(stack.Options{
|
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
||||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
|
|
||||||
})
|
|
||||||
|
|
||||||
adapter := &gvisorAdapter{
|
|
||||||
device: device,
|
|
||||||
ipstack: ipstack,
|
|
||||||
udpIn: udpIn,
|
|
||||||
stackName: conf.Stack,
|
|
||||||
autoRoute: conf.AutoRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
linkEP, err := adapter.AsLinkEndpoint()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create virtual endpoint: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ipstack.CreateNIC(nicID, linkEP); err != nil {
|
|
||||||
return nil, fmt.Errorf("fail to create NIC in ipstack: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipstack.SetPromiscuousMode(nicID, true) // Accept all the traffice from this NIC
|
|
||||||
ipstack.SetSpoofing(nicID, true) // Otherwise our TCP connection can not find the route backward
|
|
||||||
|
|
||||||
// Add route for ipv4 & ipv6
|
|
||||||
// So FindRoute will return correct route to tun NIC
|
|
||||||
subnet, _ := tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 4)), tcpip.AddressMask(strings.Repeat("\x00", 4)))
|
|
||||||
ipstack.AddRoute(tcpip.Route{Destination: subnet, Gateway: "", NIC: nicID})
|
|
||||||
subnet, _ = tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 16)), tcpip.AddressMask(strings.Repeat("\x00", 16)))
|
|
||||||
ipstack.AddRoute(tcpip.Route{Destination: subnet, Gateway: "", NIC: nicID})
|
|
||||||
|
|
||||||
// TCP handler
|
|
||||||
// maximum number of half-open tcp connection set to 1024
|
|
||||||
// receive buffer size set to 20k
|
|
||||||
tcpFwd := tcp.NewForwarder(ipstack, pool.RelayBufferSize, 1024, func(r *tcp.ForwarderRequest) {
|
|
||||||
src := net.JoinHostPort(r.ID().RemoteAddress.String(), strconv.Itoa((int)(r.ID().RemotePort)))
|
|
||||||
dst := net.JoinHostPort(r.ID().LocalAddress.String(), strconv.Itoa((int)(r.ID().LocalPort)))
|
|
||||||
log.Debugln("Get TCP Syn %v -> %s in ipstack", src, dst)
|
|
||||||
var wq waiter.Queue
|
|
||||||
ep, err := r.CreateEndpoint(&wq)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("Can't create TCP Endpoint(%s -> %s) in ipstack: %v", src, dst, err)
|
|
||||||
r.Complete(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer r.Complete(false)
|
|
||||||
|
|
||||||
conn := gonet.NewTCPConn(&wq, ep)
|
|
||||||
|
|
||||||
// if the endpoint is not in connected state, conn.RemoteAddr() will return nil
|
|
||||||
// this protection may be not enough, but will help us debug the panic
|
|
||||||
if conn.RemoteAddr() == nil {
|
|
||||||
log.Warnln("TCP endpoint is not connected, current state: %v", tcp.EndpointState(ep.State()))
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
target := getAddr(ep.Info().(*stack.TransportEndpointInfo).ID)
|
|
||||||
tcpIn <- inbound.NewSocket(target, conn, C.TUN)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpFwd.HandlePacket)
|
|
||||||
|
|
||||||
// UDP handler
|
|
||||||
ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, adapter.udpHandlePacket)
|
|
||||||
|
|
||||||
if resolver.DefaultResolver != nil {
|
|
||||||
err = adapter.ReCreateDNSServer(resolver.DefaultResolver.(*dns.Resolver), resolver.DefaultHostMapper.(*dns.ResolverEnhancer), conf.DnsHijack)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return adapter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *gvisorAdapter) Stack() string {
|
|
||||||
return t.stackName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *gvisorAdapter) AutoRoute() bool {
|
|
||||||
return t.autoRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close close the TunAdapter
|
|
||||||
func (t *gvisorAdapter) Close() {
|
|
||||||
t.StopDNSServer()
|
|
||||||
if t.ipstack != nil {
|
|
||||||
t.ipstack.Close()
|
|
||||||
}
|
|
||||||
if t.device != nil {
|
|
||||||
_ = t.device.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *gvisorAdapter) udpHandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
|
||||||
// ref: gvisor pkg/tcpip/transport/udp/endpoint.go HandlePacket
|
|
||||||
hdr := header.UDP(pkt.TransportHeader().View())
|
|
||||||
if int(hdr.Length()) > pkt.Data().Size()+header.UDPMinimumSize {
|
|
||||||
// Malformed packet.
|
|
||||||
t.ipstack.Stats().UDP.MalformedPacketsReceived.Increment()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
target := getAddr(id)
|
|
||||||
|
|
||||||
packet := &fakeConn{
|
|
||||||
id: id,
|
|
||||||
pkt: pkt,
|
|
||||||
s: t.ipstack,
|
|
||||||
payload: pkt.Data().AsRange().ToOwnedView(),
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case t.udpIn <- inbound.NewPacket(target, packet, C.TUN):
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait wait goroutines to exit
|
|
||||||
func (t *gvisorAdapter) Wait() {
|
|
||||||
t.wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *gvisorAdapter) AsLinkEndpoint() (result stack.LinkEndpoint, err error) {
|
|
||||||
if t.linkCache != nil {
|
|
||||||
return t.linkCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mtu, err := t.device.MTU()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("unable to get device mtu")
|
|
||||||
}
|
|
||||||
|
|
||||||
linkEP := channel.New(512, uint32(mtu), "")
|
|
||||||
|
|
||||||
// start Read loop. read ip packet from tun and write it to ipstack
|
|
||||||
t.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
for !t.device.IsClose() {
|
|
||||||
packet := make([]byte, mtu)
|
|
||||||
n, err := t.device.Read(packet)
|
|
||||||
if n == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil && !t.device.IsClose() {
|
|
||||||
log.Errorln("can not read from tun: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var p tcpip.NetworkProtocolNumber
|
|
||||||
switch header.IPVersion(packet) {
|
|
||||||
case header.IPv4Version:
|
|
||||||
p = header.IPv4ProtocolNumber
|
|
||||||
case header.IPv6Version:
|
|
||||||
p = header.IPv6ProtocolNumber
|
|
||||||
default:
|
|
||||||
log.Warnln("invalid IP version:%d", header.IPVersion(packet))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if linkEP.IsAttached() {
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
||||||
Data: buffer.View(packet[:n]).ToVectorisedView(),
|
|
||||||
})
|
|
||||||
|
|
||||||
linkEP.InjectInbound(p, pkt)
|
|
||||||
// release memory
|
|
||||||
pkt.DecRef()
|
|
||||||
} else {
|
|
||||||
log.Debugln("received packet from tun when %s is not attached to any dispatcher.", t.device.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.wg.Done()
|
|
||||||
t.Close()
|
|
||||||
log.Debugln("%v stop read loop", t.device.Name())
|
|
||||||
}()
|
|
||||||
|
|
||||||
// start write notification
|
|
||||||
t.writeHandle = linkEP.AddNotify(t)
|
|
||||||
t.linkCache = linkEP
|
|
||||||
return t.linkCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteNotify implements channel.Notification.WriteNotify.
|
|
||||||
func (t *gvisorAdapter) WriteNotify() {
|
|
||||||
packetBuffer := t.linkCache.Read()
|
|
||||||
if packetBuffer != nil {
|
|
||||||
var vv buffer.VectorisedView
|
|
||||||
// Append upper headers.
|
|
||||||
vv.AppendView(packetBuffer.NetworkHeader().View())
|
|
||||||
vv.AppendView(packetBuffer.TransportHeader().View())
|
|
||||||
// Append data payload.
|
|
||||||
vv.Append(packetBuffer.Data().ExtractVV())
|
|
||||||
|
|
||||||
_, err := t.device.Write(vv.ToView())
|
|
||||||
if err != nil && !t.device.IsClose() {
|
|
||||||
log.Errorln("can not write to tun: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAddr(id stack.TransportEndpointID) socks5.Addr {
|
|
||||||
ipv4 := id.LocalAddress.To4()
|
|
||||||
|
|
||||||
// get the big-endian binary represent of port
|
|
||||||
port := make([]byte, 2)
|
|
||||||
binary.BigEndian.PutUint16(port, id.LocalPort)
|
|
||||||
|
|
||||||
if ipv4 != "" {
|
|
||||||
addr := make([]byte, 1+net.IPv4len+2)
|
|
||||||
addr[0] = socks5.AtypIPv4
|
|
||||||
copy(addr[1:1+net.IPv4len], []byte(ipv4))
|
|
||||||
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
|
||||||
return addr
|
|
||||||
} else {
|
|
||||||
addr := make([]byte, 1+net.IPv6len+2)
|
|
||||||
addr[0] = socks5.AtypIPv6
|
|
||||||
copy(addr[1:1+net.IPv6len], []byte(id.LocalAddress))
|
|
||||||
addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,433 +0,0 @@
|
|||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
Common "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/dns"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
D "github.com/miekg/dns"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/ports"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ipv4Zero = tcpip.Address(net.IPv4zero.To4())
|
|
||||||
ipv6Zero = tcpip.Address(net.IPv6zero.To16())
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListenerWrap struct {
|
|
||||||
net.Listener
|
|
||||||
listener net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListenerWrap) Accept() (conn net.Conn, err error) {
|
|
||||||
conn, err = l.listener.Accept()
|
|
||||||
log.Debugln("[DNS] hijack tcp:%s", l.Addr())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListenerWrap) Close() error {
|
|
||||||
return l.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListenerWrap) Addr() net.Addr {
|
|
||||||
return l.listener.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSServer is DNS Server listening on tun devcice
|
|
||||||
type DNSServer struct {
|
|
||||||
dnsServers []*dns.Server
|
|
||||||
tcpListeners []net.Listener
|
|
||||||
resolver *dns.Resolver
|
|
||||||
stack *stack.Stack
|
|
||||||
udpEndpoints []*dnsEndpoint
|
|
||||||
udpEndpointIDs []*stack.TransportEndpointID
|
|
||||||
tcpip.NICID
|
|
||||||
}
|
|
||||||
|
|
||||||
// dnsEndpoint is a TransportEndpoint that will register to stack
|
|
||||||
type dnsEndpoint struct {
|
|
||||||
stack.TransportEndpoint
|
|
||||||
stack *stack.Stack
|
|
||||||
uniqueID uint64
|
|
||||||
server *dns.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of the source of DNS request
|
|
||||||
type dnsResponseWriter struct {
|
|
||||||
s *stack.Stack
|
|
||||||
pkt *stack.PacketBuffer // The request packet
|
|
||||||
id stack.TransportEndpointID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dnsEndpoint) UniqueID() uint64 {
|
|
||||||
return e.uniqueID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) {
|
|
||||||
hdr := header.UDP(pkt.TransportHeader().View())
|
|
||||||
if int(hdr.Length()) > pkt.Data().Size()+header.UDPMinimumSize {
|
|
||||||
// Malformed packet.
|
|
||||||
e.stack.Stats().UDP.MalformedPacketsReceived.Increment()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// server DNS
|
|
||||||
var msg D.Msg
|
|
||||||
msg.Unpack(pkt.Data().AsRange().ToOwnedView())
|
|
||||||
writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id}
|
|
||||||
log.Debugln("[DNS] hijack udp:%s:%d from %s:%d", id.LocalAddress.String(), id.LocalPort,
|
|
||||||
id.RemoteAddress.String(), id.RemotePort)
|
|
||||||
go e.server.ServeDNS(&writer, &msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dnsEndpoint) Close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dnsEndpoint) Wait() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dnsEndpoint) HandleError(transErr stack.TransportError, pkt *stack.PacketBuffer) {
|
|
||||||
log.Warnln("DNS endpoint get a transport error: %v", transErr)
|
|
||||||
log.Debugln("DNS endpoint transport error packet : %v", pkt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abort implements stack.TransportEndpoint.Abort.
|
|
||||||
func (e *dnsEndpoint) Abort() {
|
|
||||||
e.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) LocalAddr() net.Addr {
|
|
||||||
return &net.UDPAddr{IP: net.IP(w.id.LocalAddress), Port: int(w.id.LocalPort)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) RemoteAddr() net.Addr {
|
|
||||||
return &net.UDPAddr{IP: net.IP(w.id.RemoteAddress), Port: int(w.id.RemotePort)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) WriteMsg(msg *D.Msg) error {
|
|
||||||
b, err := msg.Pack()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(b)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) TsigStatus() error {
|
|
||||||
// Unsupported
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) TsigTimersOnly(bool) {
|
|
||||||
// Unsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) Hijack() {
|
|
||||||
// Unsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) Write(b []byte) (int, error) {
|
|
||||||
v := buffer.NewView(len(b))
|
|
||||||
copy(v, b)
|
|
||||||
data := v.ToVectorisedView()
|
|
||||||
|
|
||||||
// w.id.LocalAddress is the source ip of DNS response
|
|
||||||
if !w.pkt.NetworkHeader().View().IsEmpty() &&
|
|
||||||
(w.pkt.NetworkProtocolNumber == ipv4.ProtocolNumber ||
|
|
||||||
w.pkt.NetworkProtocolNumber == ipv6.ProtocolNumber) {
|
|
||||||
r, _ := w.s.FindRoute(w.pkt.NICID, w.id.LocalAddress, w.id.RemoteAddress, w.pkt.NetworkProtocolNumber, false /* multicastLoop */)
|
|
||||||
return writeUDP(r, data, w.id.LocalPort, w.id.RemotePort)
|
|
||||||
} else {
|
|
||||||
log.Debugln("the network protocl[%d] is not available", w.pkt.NetworkProtocolNumber)
|
|
||||||
return 0, fmt.Errorf("the network protocl[%d] is not available", w.pkt.NetworkProtocolNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *dnsResponseWriter) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDNSServer create a dns server on given netstack
|
|
||||||
func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijack []net.Addr, nicID tcpip.NICID) (*DNSServer, error) {
|
|
||||||
var err error
|
|
||||||
handler := dns.NewHandler(resolver, mapper)
|
|
||||||
serverIn := &dns.Server{}
|
|
||||||
serverIn.SetHandler(handler)
|
|
||||||
tcpDnsArr := make([]net.TCPAddr, 0, len(dnsHijack))
|
|
||||||
udpDnsArr := make([]net.UDPAddr, 0, len(dnsHijack))
|
|
||||||
for _, d := range dnsHijack {
|
|
||||||
switch d.(type) {
|
|
||||||
case *net.TCPAddr:
|
|
||||||
{
|
|
||||||
tcpDnsArr = append(tcpDnsArr, *d.(*net.TCPAddr))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case *net.UDPAddr:
|
|
||||||
{
|
|
||||||
udpDnsArr = append(udpDnsArr, *d.(*net.UDPAddr))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints, ids := hijackUdpDns(udpDnsArr, s, serverIn)
|
|
||||||
tcpListeners, dnsServers := hijackTcpDns(tcpDnsArr, s, serverIn)
|
|
||||||
server := &DNSServer{
|
|
||||||
resolver: resolver,
|
|
||||||
stack: s,
|
|
||||||
udpEndpoints: endpoints,
|
|
||||||
udpEndpointIDs: ids,
|
|
||||||
NICID: nicID,
|
|
||||||
tcpListeners: tcpListeners,
|
|
||||||
}
|
|
||||||
|
|
||||||
server.dnsServers = dnsServers
|
|
||||||
|
|
||||||
return server, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func hijackUdpDns(dnsArr []net.UDPAddr, s *stack.Stack, serverIn *dns.Server) ([]*dnsEndpoint, []*stack.TransportEndpointID) {
|
|
||||||
endpoints := make([]*dnsEndpoint, len(dnsArr))
|
|
||||||
ids := make([]*stack.TransportEndpointID, len(dnsArr))
|
|
||||||
for i, dns := range dnsArr {
|
|
||||||
port := dns.Port
|
|
||||||
ip := dns.IP
|
|
||||||
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
|
||||||
var protocol tcpip.NetworkProtocolNumber
|
|
||||||
if ip.To4() != nil {
|
|
||||||
address.Addr = tcpip.Address(ip.To4())
|
|
||||||
protocol = ipv4.ProtocolNumber
|
|
||||||
|
|
||||||
} else {
|
|
||||||
address.Addr = tcpip.Address(ip.To16())
|
|
||||||
protocol = ipv6.ProtocolNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
protocolAddr := tcpip.ProtocolAddress{
|
|
||||||
Protocol: protocol,
|
|
||||||
AddressWithPrefix: address.Addr.WithPrefix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints
|
|
||||||
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
|
|
||||||
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if address.Addr == ipv4Zero || address.Addr == ipv6Zero {
|
|
||||||
address.Addr = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP DNS
|
|
||||||
id := &stack.TransportEndpointID{
|
|
||||||
LocalAddress: address.Addr,
|
|
||||||
LocalPort: uint16(port),
|
|
||||||
RemotePort: 0,
|
|
||||||
RemoteAddress: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransportEndpoint for DNS
|
|
||||||
endpoint := &dnsEndpoint{
|
|
||||||
stack: s,
|
|
||||||
uniqueID: s.UniqueID(),
|
|
||||||
server: serverIn,
|
|
||||||
}
|
|
||||||
|
|
||||||
if tcpiperr := s.RegisterTransportEndpoint(
|
|
||||||
[]tcpip.NetworkProtocolNumber{
|
|
||||||
ipv4.ProtocolNumber,
|
|
||||||
ipv6.ProtocolNumber,
|
|
||||||
},
|
|
||||||
udp.ProtocolNumber,
|
|
||||||
*id,
|
|
||||||
endpoint,
|
|
||||||
ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect.
|
|
||||||
nicID); tcpiperr != nil {
|
|
||||||
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
ids[i] = id
|
|
||||||
endpoints[i] = endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoints, ids
|
|
||||||
}
|
|
||||||
|
|
||||||
func hijackTcpDns(dnsArr []net.TCPAddr, s *stack.Stack, serverIn *dns.Server) ([]net.Listener, []*dns.Server) {
|
|
||||||
tcpListeners := make([]net.Listener, len(dnsArr))
|
|
||||||
dnsServers := make([]*dns.Server, len(dnsArr))
|
|
||||||
|
|
||||||
for i, dnsAddr := range dnsArr {
|
|
||||||
var tcpListener net.Listener
|
|
||||||
var v4 bool
|
|
||||||
var err error
|
|
||||||
port := dnsAddr.Port
|
|
||||||
ip := dnsAddr.IP
|
|
||||||
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
|
||||||
if ip.To4() != nil {
|
|
||||||
address.Addr = tcpip.Address(ip.To4())
|
|
||||||
v4 = true
|
|
||||||
} else {
|
|
||||||
address.Addr = tcpip.Address(ip.To16())
|
|
||||||
v4 = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if v4 {
|
|
||||||
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
|
|
||||||
} else {
|
|
||||||
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("can not listen on tun: %v, hijack tcp[%s] failed", err, dnsAddr)
|
|
||||||
} else {
|
|
||||||
tcpListeners[i] = tcpListener
|
|
||||||
server := &D.Server{Listener: &ListenerWrap{
|
|
||||||
listener: tcpListener,
|
|
||||||
}, Handler: serverIn}
|
|
||||||
dnsServer := dns.Server{}
|
|
||||||
dnsServer.Server = server
|
|
||||||
go dnsServer.ActivateAndServe()
|
|
||||||
dnsServers[i] = &dnsServer
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//for _, listener := range tcpListeners {
|
|
||||||
// server := &D.Server{Listener: listener, Handler: serverIn}
|
|
||||||
//
|
|
||||||
// dnsServers = append(dnsServers, &dnsServer)
|
|
||||||
// go dnsServer.ActivateAndServe()
|
|
||||||
//}
|
|
||||||
|
|
||||||
return tcpListeners, dnsServers
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stop the DNS Server on tun
|
|
||||||
func (s *DNSServer) Stop() {
|
|
||||||
if s == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(s.udpEndpointIDs); i++ {
|
|
||||||
ep := s.udpEndpoints[i]
|
|
||||||
id := s.udpEndpointIDs[i]
|
|
||||||
// remove udp endpoint from stack
|
|
||||||
s.stack.UnregisterTransportEndpoint(
|
|
||||||
[]tcpip.NetworkProtocolNumber{
|
|
||||||
ipv4.ProtocolNumber,
|
|
||||||
ipv6.ProtocolNumber,
|
|
||||||
},
|
|
||||||
udp.ProtocolNumber,
|
|
||||||
*id,
|
|
||||||
ep,
|
|
||||||
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
|
|
||||||
s.NICID)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, server := range s.dnsServers {
|
|
||||||
server.Shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, listener := range s.tcpListeners {
|
|
||||||
listener.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DnsHijack return the listening address of DNS Server
|
|
||||||
func (t *gvisorAdapter) DnsHijack() []string {
|
|
||||||
dnsHijackArr := make([]string, len(t.dnsServer.udpEndpoints))
|
|
||||||
for _, id := range t.dnsServer.udpEndpointIDs {
|
|
||||||
dnsHijackArr = append(dnsHijackArr, fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
return dnsHijackArr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *gvisorAdapter) StopDNSServer() {
|
|
||||||
t.dnsServer.Stop()
|
|
||||||
log.Debugln("tun DNS server stoped")
|
|
||||||
t.dnsServer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReCreateDNSServer recreate the DNS Server on tun
|
|
||||||
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijackArr []string) error {
|
|
||||||
t.StopDNSServer()
|
|
||||||
|
|
||||||
if resolver == nil {
|
|
||||||
return fmt.Errorf("failed to create DNS server on tun: resolver not provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dnsHijackArr) == 0 {
|
|
||||||
return fmt.Errorf("failed to create DNS server on tun: len(addrs) == 0")
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var addrs []net.Addr
|
|
||||||
for _, addr := range dnsHijackArr {
|
|
||||||
var (
|
|
||||||
addrType string
|
|
||||||
hostPort string
|
|
||||||
)
|
|
||||||
|
|
||||||
addrType, hostPort, err = Common.SplitNetworkType(addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
host, port string
|
|
||||||
hasPort bool
|
|
||||||
)
|
|
||||||
|
|
||||||
host, port, hasPort, err = Common.SplitHostPort(hostPort)
|
|
||||||
if !hasPort {
|
|
||||||
port = "53"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch addrType {
|
|
||||||
case "udp", "":
|
|
||||||
{
|
|
||||||
var udpDNS *net.UDPAddr
|
|
||||||
udpDNS, err = net.ResolveUDPAddr("udp", net.JoinHostPort(host, port))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs = append(addrs, udpDNS)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tcp":
|
|
||||||
{
|
|
||||||
var tcpDNS *net.TCPAddr
|
|
||||||
tcpDNS, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs = append(addrs, tcpDNS)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unspported dns scheme:%s", addrType)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := CreateDNSServer(t.ipstack, resolver, mapper, addrs, nicID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.dnsServer = server
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
67
listener/tun/ipstack/gvisor/udp.go
Normal file
67
listener/tun/ipstack/gvisor/udp.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package gvisor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
"gvisor.dev/gvisor/pkg/waiter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withUDPHandler() Option {
|
||||||
|
return func(s *gvStack) error {
|
||||||
|
udpForwarder := udp.NewForwarder(s.Stack, func(r *udp.ForwarderRequest) {
|
||||||
|
var wq waiter.Queue
|
||||||
|
ep, err := r.CreateEndpoint(&wq)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: handler errors in the future.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &udpConn{
|
||||||
|
UDPConn: gonet.NewUDPConn(s.Stack, &wq, ep),
|
||||||
|
id: r.ID(),
|
||||||
|
}
|
||||||
|
s.handler.HandleUDPConn(conn)
|
||||||
|
})
|
||||||
|
s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpConn struct {
|
||||||
|
*gonet.UDPConn
|
||||||
|
id stack.TransportEndpointID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpConn) ID() *stack.TransportEndpointID {
|
||||||
|
return &c.id
|
||||||
|
}
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
pc adapter.UDPConn
|
||||||
|
rAddr net.Addr
|
||||||
|
payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Data() []byte {
|
||||||
|
return c.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBack write UDP packet with source(ip, port) = `addr`
|
||||||
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
return c.pc.WriteTo(b, c.rAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the source IP/Port of UDP Packet
|
||||||
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
|
return c.rAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Drop() {
|
||||||
|
pool.Put(c.payload)
|
||||||
|
}
|
@ -1,118 +0,0 @@
|
|||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeConn struct {
|
|
||||||
id stack.TransportEndpointID // The endpoint of incomming packet, it's remote address is the source address it sent from
|
|
||||||
pkt *stack.PacketBuffer // The original packet comming from tun
|
|
||||||
s *stack.Stack
|
|
||||||
payload []byte
|
|
||||||
fakeip *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeConn) Data() []byte {
|
|
||||||
return c.payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
|
||||||
v := buffer.View(b)
|
|
||||||
data := v.ToVectorisedView()
|
|
||||||
|
|
||||||
var localAddress tcpip.Address
|
|
||||||
var localPort uint16
|
|
||||||
// if addr is not provided, write back use original dst Addr as src Addr
|
|
||||||
if c.FakeIP() || addr == nil {
|
|
||||||
localAddress = c.id.LocalAddress
|
|
||||||
localPort = c.id.LocalPort
|
|
||||||
} else {
|
|
||||||
udpaddr, _ := addr.(*net.UDPAddr)
|
|
||||||
localAddress = tcpip.Address(udpaddr.IP)
|
|
||||||
localPort = uint16(udpaddr.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.pkt.NetworkHeader().View().IsEmpty() &&
|
|
||||||
(c.pkt.NetworkProtocolNumber == ipv4.ProtocolNumber ||
|
|
||||||
c.pkt.NetworkProtocolNumber == ipv6.ProtocolNumber) {
|
|
||||||
r, _ := c.s.FindRoute(c.pkt.NICID, localAddress, c.id.RemoteAddress, c.pkt.NetworkProtocolNumber, false /* multicastLoop */)
|
|
||||||
return writeUDP(r, data, localPort, c.id.RemotePort)
|
|
||||||
} else {
|
|
||||||
log.Debugln("the network protocl[%d] is not available", c.pkt.NetworkProtocolNumber)
|
|
||||||
return 0, fmt.Errorf("the network protocl[%d] is not available", c.pkt.NetworkProtocolNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeConn) LocalAddr() net.Addr {
|
|
||||||
return &net.UDPAddr{IP: net.IP(c.id.RemoteAddress), Port: int(c.id.RemotePort)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeConn) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeConn) Drop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeConn) FakeIP() bool {
|
|
||||||
if c.fakeip != nil {
|
|
||||||
return *c.fakeip
|
|
||||||
}
|
|
||||||
fakeip := resolver.IsFakeIP(net.IP(c.id.LocalAddress.To4()))
|
|
||||||
c.fakeip = &fakeip
|
|
||||||
return fakeip
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeUDP(r *stack.Route, data buffer.VectorisedView, localPort, remotePort uint16) (int, error) {
|
|
||||||
const protocol = udp.ProtocolNumber
|
|
||||||
// Allocate a buffer for the UDP header.
|
|
||||||
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
||||||
ReserveHeaderBytes: header.UDPMinimumSize + int(r.MaxHeaderLength()),
|
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Initialize the header.
|
|
||||||
udp := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
|
|
||||||
|
|
||||||
length := uint16(pkt.Size())
|
|
||||||
udp.Encode(&header.UDPFields{
|
|
||||||
SrcPort: localPort,
|
|
||||||
DstPort: remotePort,
|
|
||||||
Length: length,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set the checksum field unless TX checksum offload is enabled.
|
|
||||||
// On IPv4, UDP checksum is optional, and a zero value indicates the
|
|
||||||
// transmitter skipped the checksum generation (RFC768).
|
|
||||||
// On IPv6, UDP checksum is not optional (RFC2460 Section 8.1).
|
|
||||||
if r.RequiresTXTransportChecksum() {
|
|
||||||
xsum := r.PseudoHeaderChecksum(protocol, length)
|
|
||||||
for _, v := range data.Views() {
|
|
||||||
xsum = header.Checksum(v, xsum)
|
|
||||||
}
|
|
||||||
udp.SetChecksum(^udp.CalculateChecksum(xsum))
|
|
||||||
}
|
|
||||||
|
|
||||||
ttl := r.DefaultTTL()
|
|
||||||
|
|
||||||
if err := r.WritePacket(stack.NetworkHeaderParams{Protocol: protocol, TTL: ttl, TOS: 0 /* default */}, pkt); err != nil {
|
|
||||||
r.Stats().UDP.PacketSendErrors.Increment()
|
|
||||||
return 0, fmt.Errorf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track count of packets sent.
|
|
||||||
r.Stats().UDP.PacketsSent.Increment()
|
|
||||||
return data.Size(), nil
|
|
||||||
}
|
|
5
listener/tun/ipstack/stack.go
Normal file
5
listener/tun/ipstack/stack.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ipstack
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type Stack io.Closer
|
@ -1,9 +0,0 @@
|
|||||||
package ipstack
|
|
||||||
|
|
||||||
// TunAdapter hold the state of tun/tap interface
|
|
||||||
type TunAdapter interface {
|
|
||||||
Close()
|
|
||||||
Stack() string
|
|
||||||
DnsHijack() []string
|
|
||||||
AutoRoute() bool
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
|
||||||
"github.com/kr328/tun2socket/binding"
|
|
||||||
"github.com/kr328/tun2socket/redirect"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultDnsReadTimeout = time.Second * 30
|
|
||||||
|
|
||||||
func shouldHijackDns(dnsAddr binding.Address, targetAddr binding.Address) bool {
|
|
||||||
if targetAddr.Port != 53 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return dnsAddr.IP.Equal(net.IPv4zero) || dnsAddr.IP.Equal(targetAddr.IP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
|
||||||
go func() {
|
|
||||||
answer, err := D.RelayDnsPacket(pkt)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = sender(answer, &binding.Endpoint{
|
|
||||||
Source: ep.Target,
|
|
||||||
Target: ep.Source,
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func hijackTCPDns(conn net.Conn) {
|
|
||||||
go func() {
|
|
||||||
defer func(conn net.Conn) {
|
|
||||||
_ = conn.Close()
|
|
||||||
}(conn)
|
|
||||||
|
|
||||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
var length uint16
|
|
||||||
if binary.Read(conn, binary.BigEndian, &length) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]byte, length)
|
|
||||||
|
|
||||||
_, err := io.ReadFull(conn, data)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rb, err := D.RelayDnsPacket(data)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = conn.Write(rb); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import "github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
type logger struct{}
|
|
||||||
|
|
||||||
func (l *logger) D(format string, args ...interface{}) {
|
|
||||||
log.Debugln("[TUN] "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) I(format string, args ...interface{}) {
|
|
||||||
log.Infoln("[TUN] "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) W(format string, args ...interface{}) {
|
|
||||||
log.Warnln("[TUN] "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) E(format string, args ...interface{}) {
|
|
||||||
log.Errorln("[TUN] "+format, args...)
|
|
||||||
}
|
|
42
listener/tun/ipstack/system/mars/mars.go
Normal file
42
listener/tun/ipstack/system/mars/mars.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package mars
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/nat"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StackListener struct {
|
||||||
|
device io.Closer
|
||||||
|
tcp *nat.TCP
|
||||||
|
udp *nat.UDP
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartListener(device io.ReadWriteCloser, gateway netip.Addr, portal netip.Addr) (*StackListener, error) {
|
||||||
|
tcp, udp, err := nat.Start(device, gateway, portal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StackListener{
|
||||||
|
device: device,
|
||||||
|
tcp: tcp,
|
||||||
|
udp: udp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StackListener) Close() error {
|
||||||
|
_ = t.tcp.Close()
|
||||||
|
_ = t.udp.Close()
|
||||||
|
|
||||||
|
return t.device.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StackListener) TCP() *nat.TCP {
|
||||||
|
return t.tcp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StackListener) UDP() *nat.UDP {
|
||||||
|
return t.udp
|
||||||
|
}
|
193
listener/tun/ipstack/system/mars/nat/nat.go
Normal file
193
listener/tun/ipstack/system/mars/nat/nat.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package nat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(
|
||||||
|
device io.ReadWriter,
|
||||||
|
gateway netip.Addr,
|
||||||
|
portal netip.Addr,
|
||||||
|
) (*TCP, *UDP, error) {
|
||||||
|
if !portal.Is4() || !gateway.Is4() {
|
||||||
|
return nil, nil, net.InvalidAddrError("only ipv4 supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.ListenTCP("tcp4", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tab := newTable()
|
||||||
|
udp := &UDP{
|
||||||
|
device: device,
|
||||||
|
buf: [pool.UDPBufferSize]byte{},
|
||||||
|
}
|
||||||
|
tcp := &TCP{
|
||||||
|
listener: listener,
|
||||||
|
portal: portal,
|
||||||
|
table: tab,
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayPort := uint16(listener.Addr().(*net.TCPAddr).Port)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer tcp.Close()
|
||||||
|
defer udp.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, pool.RelayBufferSize)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := device.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := buf[:n]
|
||||||
|
|
||||||
|
var (
|
||||||
|
ipVersion int
|
||||||
|
ip tcpip.IP
|
||||||
|
)
|
||||||
|
|
||||||
|
ipVersion = tcpip.IPVersion(raw)
|
||||||
|
|
||||||
|
switch ipVersion {
|
||||||
|
case tcpip.IPv4Version:
|
||||||
|
ipv4 := tcpip.IPv4Packet(raw)
|
||||||
|
if !ipv4.Valid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4.TimeToLive() == 0x00 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4.Flags()&tcpip.FlagMoreFragment != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4.Offset() != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ipv4
|
||||||
|
case tcpip.IPv6Version:
|
||||||
|
ipv6 := tcpip.IPv6Packet(raw)
|
||||||
|
if !ipv6.Valid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6.HopLimit() == 0x00 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ipv6
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ip.Protocol() {
|
||||||
|
case tcpip.TCP:
|
||||||
|
t := tcpip.TCPPacket(ip.Payload())
|
||||||
|
if !t.Valid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.DestinationIP() == portal {
|
||||||
|
if ip.SourceIP() == gateway && t.SourcePort() == gatewayPort {
|
||||||
|
tup := tab.tupleOf(t.DestinationPort())
|
||||||
|
if tup == zeroTuple {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip.SetSourceIP(tup.DestinationAddr.Addr())
|
||||||
|
t.SetSourcePort(tup.DestinationAddr.Port())
|
||||||
|
ip.SetDestinationIP(tup.SourceAddr.Addr())
|
||||||
|
t.SetDestinationPort(tup.SourceAddr.Port())
|
||||||
|
|
||||||
|
ip.DecTimeToLive()
|
||||||
|
ip.ResetChecksum()
|
||||||
|
t.ResetChecksum(ip.PseudoSum())
|
||||||
|
|
||||||
|
_, _ = device.Write(raw)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tup := tuple{
|
||||||
|
SourceAddr: netip.AddrPortFrom(ip.SourceIP(), t.SourcePort()),
|
||||||
|
DestinationAddr: netip.AddrPortFrom(ip.DestinationIP(), t.DestinationPort()),
|
||||||
|
}
|
||||||
|
|
||||||
|
port := tab.portOf(tup)
|
||||||
|
if port == 0 {
|
||||||
|
if t.Flags() != tcpip.TCPSyn {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
port = tab.newConn(tup)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip.SetSourceIP(portal)
|
||||||
|
ip.SetDestinationIP(gateway)
|
||||||
|
t.SetSourcePort(port)
|
||||||
|
t.SetDestinationPort(gatewayPort)
|
||||||
|
|
||||||
|
ip.ResetChecksum()
|
||||||
|
t.ResetChecksum(ip.PseudoSum())
|
||||||
|
|
||||||
|
_, _ = device.Write(raw)
|
||||||
|
}
|
||||||
|
case tcpip.UDP:
|
||||||
|
u := tcpip.UDPPacket(ip.Payload())
|
||||||
|
if !u.Valid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
udp.handleUDPPacket(ip, u)
|
||||||
|
case tcpip.ICMP:
|
||||||
|
i := tcpip.ICMPPacket(ip.Payload())
|
||||||
|
|
||||||
|
if i.Type() != tcpip.ICMPTypePingRequest || i.Code() != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i.SetType(tcpip.ICMPTypePingResponse)
|
||||||
|
|
||||||
|
source := ip.SourceIP()
|
||||||
|
destination := ip.DestinationIP()
|
||||||
|
ip.SetSourceIP(destination)
|
||||||
|
ip.SetDestinationIP(source)
|
||||||
|
|
||||||
|
ip.ResetChecksum()
|
||||||
|
i.ResetChecksum()
|
||||||
|
|
||||||
|
_, _ = device.Write(raw)
|
||||||
|
case tcpip.ICMPv6:
|
||||||
|
i := tcpip.ICMPv6Packet(ip.Payload())
|
||||||
|
|
||||||
|
if i.Type() != tcpip.ICMPv6EchoRequest || i.Code() != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i.SetType(tcpip.ICMPv6EchoReply)
|
||||||
|
|
||||||
|
source := ip.SourceIP()
|
||||||
|
destination := ip.DestinationIP()
|
||||||
|
ip.SetSourceIP(destination)
|
||||||
|
ip.SetDestinationIP(source)
|
||||||
|
|
||||||
|
ip.ResetChecksum()
|
||||||
|
i.ResetChecksum(ip.PseudoSum())
|
||||||
|
|
||||||
|
_, _ = device.Write(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return tcp, udp, nil
|
||||||
|
}
|
83
listener/tun/ipstack/system/mars/nat/table.go
Normal file
83
listener/tun/ipstack/system/mars/nat/table.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package nat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
portBegin = 30000
|
||||||
|
portLength = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
var zeroTuple = tuple{}
|
||||||
|
|
||||||
|
type tuple struct {
|
||||||
|
SourceAddr netip.AddrPort
|
||||||
|
DestinationAddr netip.AddrPort
|
||||||
|
}
|
||||||
|
|
||||||
|
type binding struct {
|
||||||
|
tuple tuple
|
||||||
|
offset uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type table struct {
|
||||||
|
tuples map[tuple]*list.Element
|
||||||
|
ports [portLength]*list.Element
|
||||||
|
available *list.List
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *table) tupleOf(port uint16) tuple {
|
||||||
|
offset := port - portBegin
|
||||||
|
if offset > portLength {
|
||||||
|
return zeroTuple
|
||||||
|
}
|
||||||
|
|
||||||
|
elm := t.ports[offset]
|
||||||
|
|
||||||
|
t.available.MoveToFront(elm)
|
||||||
|
|
||||||
|
return elm.Value.(*binding).tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *table) portOf(tuple tuple) uint16 {
|
||||||
|
elm := t.tuples[tuple]
|
||||||
|
if elm == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
t.available.MoveToFront(elm)
|
||||||
|
|
||||||
|
return portBegin + elm.Value.(*binding).offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *table) newConn(tuple tuple) uint16 {
|
||||||
|
elm := t.available.Back()
|
||||||
|
b := elm.Value.(*binding)
|
||||||
|
|
||||||
|
delete(t.tuples, b.tuple)
|
||||||
|
t.tuples[tuple] = elm
|
||||||
|
b.tuple = tuple
|
||||||
|
|
||||||
|
t.available.MoveToFront(elm)
|
||||||
|
|
||||||
|
return portBegin + b.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTable() *table {
|
||||||
|
result := &table{
|
||||||
|
tuples: make(map[tuple]*list.Element, portLength),
|
||||||
|
ports: [portLength]*list.Element{},
|
||||||
|
available: list.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := range result.ports {
|
||||||
|
result.ports[idx] = result.available.PushFront(&binding{
|
||||||
|
tuple: tuple{},
|
||||||
|
offset: uint16(idx),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
67
listener/tun/ipstack/system/mars/nat/tcp.go
Normal file
67
listener/tun/ipstack/system/mars/nat/tcp.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package nat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCP struct {
|
||||||
|
listener *net.TCPListener
|
||||||
|
portal netip.Addr
|
||||||
|
table *table
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
tuple tuple
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCP) Accept() (net.Conn, error) {
|
||||||
|
c, err := t.listener.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := c.RemoteAddr().(*net.TCPAddr)
|
||||||
|
tup := t.table.tupleOf(uint16(addr.Port))
|
||||||
|
if !addr.IP.Equal(t.portal.AsSlice()) || tup == zeroTuple {
|
||||||
|
_ = c.Close()
|
||||||
|
|
||||||
|
return nil, net.InvalidAddrError("unknown remote addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
addition(c)
|
||||||
|
|
||||||
|
return &conn{
|
||||||
|
Conn: c,
|
||||||
|
tuple: tup,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCP) Close() error {
|
||||||
|
return t.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCP) Addr() net.Addr {
|
||||||
|
return t.listener.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCP) SetDeadline(time time.Time) error {
|
||||||
|
return t.listener.SetDeadline(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) LocalAddr() net.Addr {
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: c.tuple.SourceAddr.Addr().AsSlice(),
|
||||||
|
Port: int(c.tuple.SourceAddr.Port()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) RemoteAddr() net.Addr {
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: c.tuple.DestinationAddr.Addr().AsSlice(),
|
||||||
|
Port: int(c.tuple.DestinationAddr.Port()),
|
||||||
|
}
|
||||||
|
}
|
15
listener/tun/ipstack/system/mars/nat/tcp_linux.go
Normal file
15
listener/tun/ipstack/system/mars/nat/tcp_linux.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package nat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addition(c *net.TCPConn) {
|
||||||
|
sys, err := c.SyscallConn()
|
||||||
|
if err == nil {
|
||||||
|
_ = sys.Control(func(fd uintptr) {
|
||||||
|
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_NO_CHECK, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
7
listener/tun/ipstack/system/mars/nat/tcp_others.go
Normal file
7
listener/tun/ipstack/system/mars/nat/tcp_others.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package nat
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func addition(*net.TCPConn) {}
|
147
listener/tun/ipstack/system/mars/nat/udp.go
Normal file
147
listener/tun/ipstack/system/mars/nat/udp.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package nat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type call struct {
|
||||||
|
cond *sync.Cond
|
||||||
|
buf []byte
|
||||||
|
n int
|
||||||
|
source net.Addr
|
||||||
|
destination net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDP struct {
|
||||||
|
closed bool
|
||||||
|
device io.Writer
|
||||||
|
queueLock sync.Mutex
|
||||||
|
queue []*call
|
||||||
|
bufLock sync.Mutex
|
||||||
|
buf [pool.UDPBufferSize]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) {
|
||||||
|
u.queueLock.Lock()
|
||||||
|
defer u.queueLock.Unlock()
|
||||||
|
|
||||||
|
for !u.closed {
|
||||||
|
c := &call{
|
||||||
|
cond: sync.NewCond(&u.queueLock),
|
||||||
|
buf: buf,
|
||||||
|
n: -1,
|
||||||
|
source: nil,
|
||||||
|
destination: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
u.queue = append(u.queue, c)
|
||||||
|
|
||||||
|
c.cond.Wait()
|
||||||
|
|
||||||
|
if c.n >= 0 {
|
||||||
|
return c.n, c.source, c.destination, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, nil, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error) {
|
||||||
|
if u.closed {
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
u.bufLock.Lock()
|
||||||
|
defer u.bufLock.Unlock()
|
||||||
|
|
||||||
|
if len(buf) > 0xffff {
|
||||||
|
return 0, net.InvalidAddrError("invalid ip version")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcAddr, srcOk := local.(*net.UDPAddr)
|
||||||
|
dstAddr, dstOk := remote.(*net.UDPAddr)
|
||||||
|
if !srcOk || !dstOk {
|
||||||
|
return 0, net.InvalidAddrError("invalid addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcIP, _ := netip.AddrFromSlice(srcAddr.IP)
|
||||||
|
dstIp, _ := netip.AddrFromSlice(dstAddr.IP)
|
||||||
|
|
||||||
|
srcAddrPort := netip.AddrPortFrom(srcIP, uint16(srcAddr.Port))
|
||||||
|
dstAddrPort := netip.AddrPortFrom(dstIp, uint16(dstAddr.Port))
|
||||||
|
|
||||||
|
if !srcAddrPort.Addr().Is4() || !dstAddrPort.Addr().Is4() {
|
||||||
|
return 0, net.InvalidAddrError("invalid ip version")
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpip.SetIPv4(u.buf[:])
|
||||||
|
|
||||||
|
ip := tcpip.IPv4Packet(u.buf[:])
|
||||||
|
ip.SetHeaderLen(tcpip.IPv4HeaderSize)
|
||||||
|
ip.SetTotalLength(tcpip.IPv4HeaderSize + tcpip.UDPHeaderSize + uint16(len(buf)))
|
||||||
|
ip.SetTypeOfService(0)
|
||||||
|
ip.SetIdentification(uint16(rand.Uint32()))
|
||||||
|
ip.SetFragmentOffset(0)
|
||||||
|
ip.SetTimeToLive(64)
|
||||||
|
ip.SetProtocol(tcpip.UDP)
|
||||||
|
ip.SetSourceIP(srcAddrPort.Addr())
|
||||||
|
ip.SetDestinationIP(dstAddrPort.Addr())
|
||||||
|
|
||||||
|
udp := tcpip.UDPPacket(ip.Payload())
|
||||||
|
udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf)))
|
||||||
|
udp.SetSourcePort(srcAddrPort.Port())
|
||||||
|
udp.SetDestinationPort(dstAddrPort.Port())
|
||||||
|
copy(udp.Payload(), buf)
|
||||||
|
|
||||||
|
ip.ResetChecksum()
|
||||||
|
udp.ResetChecksum(ip.PseudoSum())
|
||||||
|
|
||||||
|
return u.device.Write(u.buf[:ip.TotalLen()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UDP) Close() error {
|
||||||
|
u.queueLock.Lock()
|
||||||
|
defer u.queueLock.Unlock()
|
||||||
|
|
||||||
|
u.closed = true
|
||||||
|
|
||||||
|
for _, c := range u.queue {
|
||||||
|
c.cond.Signal()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UDP) handleUDPPacket(ip tcpip.IP, pkt tcpip.UDPPacket) {
|
||||||
|
var c *call
|
||||||
|
|
||||||
|
u.queueLock.Lock()
|
||||||
|
|
||||||
|
if len(u.queue) > 0 {
|
||||||
|
idx := len(u.queue) - 1
|
||||||
|
c = u.queue[idx]
|
||||||
|
u.queue = u.queue[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
u.queueLock.Unlock()
|
||||||
|
|
||||||
|
if c != nil {
|
||||||
|
c.source = &net.UDPAddr{
|
||||||
|
IP: ip.SourceIP().AsSlice(),
|
||||||
|
Port: int(pkt.SourcePort()),
|
||||||
|
}
|
||||||
|
c.destination = &net.UDPAddr{
|
||||||
|
IP: ip.DestinationIP().AsSlice(),
|
||||||
|
Port: int(pkt.DestinationPort()),
|
||||||
|
}
|
||||||
|
c.n = copy(c.buf, pkt.Payload())
|
||||||
|
c.cond.Signal()
|
||||||
|
}
|
||||||
|
}
|
40
listener/tun/ipstack/system/mars/tcpip/icmp.go
Normal file
40
listener/tun/ipstack/system/mars/tcpip/icmp.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package tcpip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ICMPType = byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICMPTypePingRequest byte = 0x8
|
||||||
|
ICMPTypePingResponse byte = 0x0
|
||||||
|
)
|
||||||
|
|
||||||
|
type ICMPPacket []byte
|
||||||
|
|
||||||
|
func (p ICMPPacket) Type() ICMPType {
|
||||||
|
return p[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ICMPPacket) SetType(v ICMPType) {
|
||||||
|
p[0] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ICMPPacket) Code() byte {
|
||||||
|
return p[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ICMPPacket) Checksum() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ICMPPacket) SetChecksum(sum [2]byte) {
|
||||||
|
p[2] = sum[0]
|
||||||
|
p[3] = sum[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ICMPPacket) ResetChecksum() {
|
||||||
|
p.SetChecksum(zeroChecksum)
|
||||||
|
p.SetChecksum(Checksum(0, p))
|
||||||
|
}
|
172
listener/tun/ipstack/system/mars/tcpip/icmpv6.go
Normal file
172
listener/tun/ipstack/system/mars/tcpip/icmpv6.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package tcpip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ICMPv6Packet []byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICMPv6HeaderSize = 4
|
||||||
|
|
||||||
|
ICMPv6MinimumSize = 8
|
||||||
|
|
||||||
|
ICMPv6PayloadOffset = 8
|
||||||
|
|
||||||
|
ICMPv6EchoMinimumSize = 8
|
||||||
|
|
||||||
|
ICMPv6ErrorHeaderSize = 8
|
||||||
|
|
||||||
|
ICMPv6DstUnreachableMinimumSize = ICMPv6MinimumSize
|
||||||
|
|
||||||
|
ICMPv6PacketTooBigMinimumSize = ICMPv6MinimumSize
|
||||||
|
|
||||||
|
ICMPv6ChecksumOffset = 2
|
||||||
|
|
||||||
|
icmpv6PointerOffset = 4
|
||||||
|
|
||||||
|
icmpv6MTUOffset = 4
|
||||||
|
|
||||||
|
icmpv6IdentOffset = 4
|
||||||
|
|
||||||
|
icmpv6SequenceOffset = 6
|
||||||
|
|
||||||
|
NDPHopLimit = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
type ICMPv6Type byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICMPv6DstUnreachable ICMPv6Type = 1
|
||||||
|
ICMPv6PacketTooBig ICMPv6Type = 2
|
||||||
|
ICMPv6TimeExceeded ICMPv6Type = 3
|
||||||
|
ICMPv6ParamProblem ICMPv6Type = 4
|
||||||
|
ICMPv6EchoRequest ICMPv6Type = 128
|
||||||
|
ICMPv6EchoReply ICMPv6Type = 129
|
||||||
|
|
||||||
|
ICMPv6RouterSolicit ICMPv6Type = 133
|
||||||
|
ICMPv6RouterAdvert ICMPv6Type = 134
|
||||||
|
ICMPv6NeighborSolicit ICMPv6Type = 135
|
||||||
|
ICMPv6NeighborAdvert ICMPv6Type = 136
|
||||||
|
ICMPv6RedirectMsg ICMPv6Type = 137
|
||||||
|
|
||||||
|
ICMPv6MulticastListenerQuery ICMPv6Type = 130
|
||||||
|
ICMPv6MulticastListenerReport ICMPv6Type = 131
|
||||||
|
ICMPv6MulticastListenerDone ICMPv6Type = 132
|
||||||
|
)
|
||||||
|
|
||||||
|
func (typ ICMPv6Type) IsErrorType() bool {
|
||||||
|
return typ&0x80 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type ICMPv6Code byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICMPv6NetworkUnreachable ICMPv6Code = 0
|
||||||
|
ICMPv6Prohibited ICMPv6Code = 1
|
||||||
|
ICMPv6BeyondScope ICMPv6Code = 2
|
||||||
|
ICMPv6AddressUnreachable ICMPv6Code = 3
|
||||||
|
ICMPv6PortUnreachable ICMPv6Code = 4
|
||||||
|
ICMPv6Policy ICMPv6Code = 5
|
||||||
|
ICMPv6RejectRoute ICMPv6Code = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICMPv6HopLimitExceeded ICMPv6Code = 0
|
||||||
|
ICMPv6ReassemblyTimeout ICMPv6Code = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICMPv6ErroneousHeader ICMPv6Code = 0
|
||||||
|
|
||||||
|
ICMPv6UnknownHeader ICMPv6Code = 1
|
||||||
|
|
||||||
|
ICMPv6UnknownOption ICMPv6Code = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const ICMPv6UnusedCode ICMPv6Code = 0
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) Type() ICMPv6Type {
|
||||||
|
return ICMPv6Type(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetType(t ICMPv6Type) {
|
||||||
|
b[0] = byte(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) Code() ICMPv6Code {
|
||||||
|
return ICMPv6Code(b[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetCode(c ICMPv6Code) {
|
||||||
|
b[1] = byte(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) TypeSpecific() uint32 {
|
||||||
|
return binary.BigEndian.Uint32(b[icmpv6PointerOffset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetTypeSpecific(val uint32) {
|
||||||
|
binary.BigEndian.PutUint32(b[icmpv6PointerOffset:], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) Checksum() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(b[ICMPv6ChecksumOffset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetChecksum(sum [2]byte) {
|
||||||
|
_ = b[ICMPv6ChecksumOffset+1]
|
||||||
|
b[ICMPv6ChecksumOffset] = sum[0]
|
||||||
|
b[ICMPv6ChecksumOffset+1] = sum[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ICMPv6Packet) SourcePort() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ICMPv6Packet) DestinationPort() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ICMPv6Packet) SetSourcePort(uint16) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ICMPv6Packet) SetDestinationPort(uint16) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) MTU() uint32 {
|
||||||
|
return binary.BigEndian.Uint32(b[icmpv6MTUOffset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetMTU(mtu uint32) {
|
||||||
|
binary.BigEndian.PutUint32(b[icmpv6MTUOffset:], mtu)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) Ident() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(b[icmpv6IdentOffset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetIdent(ident uint16) {
|
||||||
|
binary.BigEndian.PutUint16(b[icmpv6IdentOffset:], ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) Sequence() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(b[icmpv6SequenceOffset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) SetSequence(sequence uint16) {
|
||||||
|
binary.BigEndian.PutUint16(b[icmpv6SequenceOffset:], sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) MessageBody() []byte {
|
||||||
|
return b[ICMPv6HeaderSize:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) Payload() []byte {
|
||||||
|
return b[ICMPv6PayloadOffset:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ICMPv6Packet) ResetChecksum(psum uint32) {
|
||||||
|
b.SetChecksum(zeroChecksum)
|
||||||
|
b.SetChecksum(Checksum(psum, b))
|
||||||
|
}
|
215
listener/tun/ipstack/system/mars/tcpip/ip.go
Normal file
215
listener/tun/ipstack/system/mars/tcpip/ip.go
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
package tcpip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPProtocol = byte
|
||||||
|
|
||||||
|
type IP interface {
|
||||||
|
Payload() []byte
|
||||||
|
SourceIP() netip.Addr
|
||||||
|
DestinationIP() netip.Addr
|
||||||
|
SetSourceIP(ip netip.Addr)
|
||||||
|
SetDestinationIP(ip netip.Addr)
|
||||||
|
Protocol() IPProtocol
|
||||||
|
DecTimeToLive()
|
||||||
|
ResetChecksum()
|
||||||
|
PseudoSum() uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPProtocol type
|
||||||
|
const (
|
||||||
|
ICMP IPProtocol = 0x01
|
||||||
|
TCP IPProtocol = 0x06
|
||||||
|
UDP IPProtocol = 0x11
|
||||||
|
ICMPv6 IPProtocol = 0x3a
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlagDontFragment = 1 << 1
|
||||||
|
FlagMoreFragment = 1 << 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPv4HeaderSize = 20
|
||||||
|
|
||||||
|
IPv4Version = 4
|
||||||
|
|
||||||
|
IPv4OptionsOffset = 20
|
||||||
|
IPv4PacketMinLength = IPv4OptionsOffset
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidLength = errors.New("invalid packet length")
|
||||||
|
ErrInvalidIPVersion = errors.New("invalid ip version")
|
||||||
|
ErrInvalidChecksum = errors.New("invalid checksum")
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPv4Packet []byte
|
||||||
|
|
||||||
|
func (p IPv4Packet) TotalLen() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetTotalLength(length uint16) {
|
||||||
|
binary.BigEndian.PutUint16(p[2:], length)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) HeaderLen() uint16 {
|
||||||
|
return uint16(p[0]&0xf) * 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetHeaderLen(length uint16) {
|
||||||
|
p[0] &= 0xF0
|
||||||
|
p[0] |= byte(length / 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) TypeOfService() byte {
|
||||||
|
return p[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetTypeOfService(tos byte) {
|
||||||
|
p[1] = tos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Identification() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p[4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetIdentification(id uint16) {
|
||||||
|
binary.BigEndian.PutUint16(p[4:], id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) FragmentOffset() uint16 {
|
||||||
|
return binary.BigEndian.Uint16([]byte{p[6] & 0x7, p[7]}) * 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetFragmentOffset(offset uint32) {
|
||||||
|
flags := p.Flags()
|
||||||
|
binary.BigEndian.PutUint16(p[6:], uint16(offset/8))
|
||||||
|
p.SetFlags(flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) DataLen() uint16 {
|
||||||
|
return p.TotalLen() - p.HeaderLen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Payload() []byte {
|
||||||
|
return p[p.HeaderLen():p.TotalLen()]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Protocol() IPProtocol {
|
||||||
|
return p[9]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetProtocol(protocol IPProtocol) {
|
||||||
|
p[9] = protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Flags() byte {
|
||||||
|
return p[6] >> 5
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetFlags(flags byte) {
|
||||||
|
p[6] &= 0x1F
|
||||||
|
p[6] |= flags << 5
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Offset() uint16 {
|
||||||
|
offset := binary.BigEndian.Uint16(p[6:8])
|
||||||
|
|
||||||
|
return (offset & 0x1fff) * 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SourceIP() netip.Addr {
|
||||||
|
return netip.AddrFrom4([4]byte{p[12], p[13], p[14], p[15]})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetSourceIP(ip netip.Addr) {
|
||||||
|
if ip.Is4() {
|
||||||
|
copy(p[12:16], ip.AsSlice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) DestinationIP() netip.Addr {
|
||||||
|
return netip.AddrFrom4([4]byte{p[16], p[17], p[18], p[19]})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetDestinationIP(ip netip.Addr) {
|
||||||
|
if ip.Is4() {
|
||||||
|
copy(p[16:20], ip.AsSlice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Checksum() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetChecksum(sum [2]byte) {
|
||||||
|
p[10] = sum[0]
|
||||||
|
p[11] = sum[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) TimeToLive() uint8 {
|
||||||
|
return p[8]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) SetTimeToLive(ttl uint8) {
|
||||||
|
p[8] = ttl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) DecTimeToLive() {
|
||||||
|
p[8] = p[8] - uint8(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) ResetChecksum() {
|
||||||
|
p.SetChecksum(zeroChecksum)
|
||||||
|
p.SetChecksum(Checksum(0, p[:p.HeaderLen()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PseudoSum for tcp checksum
|
||||||
|
func (p IPv4Packet) PseudoSum() uint32 {
|
||||||
|
sum := Sum(p[12:20])
|
||||||
|
sum += uint32(p.Protocol())
|
||||||
|
sum += uint32(p.DataLen())
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Valid() bool {
|
||||||
|
return len(p) >= IPv4HeaderSize && uint16(len(p)) >= p.TotalLen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p IPv4Packet) Verify() error {
|
||||||
|
if len(p) < IPv4PacketMinLength {
|
||||||
|
return ErrInvalidLength
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum := []byte{p[10], p[11]}
|
||||||
|
headerLength := uint16(p[0]&0xF) * 4
|
||||||
|
packetLength := binary.BigEndian.Uint16(p[2:])
|
||||||
|
|
||||||
|
if p[0]>>4 != 4 {
|
||||||
|
return ErrInvalidIPVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint16(len(p)) < packetLength || packetLength < headerLength {
|
||||||
|
return ErrInvalidLength
|
||||||
|
}
|
||||||
|
|
||||||
|
p[10] = 0
|
||||||
|
p[11] = 0
|
||||||
|
defer copy(p[10:12], checksum)
|
||||||
|
|
||||||
|
answer := Checksum(0, p[:headerLength])
|
||||||
|
|
||||||
|
if answer[0] != checksum[0] || answer[1] != checksum[1] {
|
||||||
|
return ErrInvalidChecksum
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ IP = (*IPv4Packet)(nil)
|
141
listener/tun/ipstack/system/mars/tcpip/ipv6.go
Normal file
141
listener/tun/ipstack/system/mars/tcpip/ipv6.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package tcpip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
versTCFL = 0
|
||||||
|
|
||||||
|
IPv6PayloadLenOffset = 4
|
||||||
|
|
||||||
|
IPv6NextHeaderOffset = 6
|
||||||
|
hopLimit = 7
|
||||||
|
v6SrcAddr = 8
|
||||||
|
v6DstAddr = v6SrcAddr + IPv6AddressSize
|
||||||
|
|
||||||
|
IPv6FixedHeaderSize = v6DstAddr + IPv6AddressSize
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
versIHL = 0
|
||||||
|
tos = 1
|
||||||
|
ipVersionShift = 4
|
||||||
|
ipIHLMask = 0x0f
|
||||||
|
IPv4IHLStride = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPv6Packet []byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPv6MinimumSize = IPv6FixedHeaderSize
|
||||||
|
|
||||||
|
IPv6AddressSize = 16
|
||||||
|
|
||||||
|
IPv6Version = 6
|
||||||
|
|
||||||
|
IPv6MinimumMTU = 1280
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b IPv6Packet) PayloadLength() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(b[IPv6PayloadLenOffset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) HopLimit() uint8 {
|
||||||
|
return b[hopLimit]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) NextHeader() byte {
|
||||||
|
return b[IPv6NextHeaderOffset]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) Protocol() IPProtocol {
|
||||||
|
return b.NextHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) Payload() []byte {
|
||||||
|
return b[IPv6MinimumSize:][:b.PayloadLength()]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SourceIP() netip.Addr {
|
||||||
|
addr, _ := netip.AddrFromSlice(b[v6SrcAddr:][:IPv6AddressSize])
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) DestinationIP() netip.Addr {
|
||||||
|
addr, _ := netip.AddrFromSlice(b[v6DstAddr:][:IPv6AddressSize])
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IPv6Packet) Checksum() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) TOS() (uint8, uint32) {
|
||||||
|
v := binary.BigEndian.Uint32(b[versTCFL:])
|
||||||
|
return uint8(v >> 20), v & 0xfffff
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetTOS(t uint8, l uint32) {
|
||||||
|
vtf := (6 << 28) | (uint32(t) << 20) | (l & 0xfffff)
|
||||||
|
binary.BigEndian.PutUint32(b[versTCFL:], vtf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetPayloadLength(payloadLength uint16) {
|
||||||
|
binary.BigEndian.PutUint16(b[IPv6PayloadLenOffset:], payloadLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetSourceIP(addr netip.Addr) {
|
||||||
|
if addr.Is6() {
|
||||||
|
copy(b[v6SrcAddr:][:IPv6AddressSize], addr.AsSlice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetDestinationIP(addr netip.Addr) {
|
||||||
|
if addr.Is6() {
|
||||||
|
copy(b[v6DstAddr:][:IPv6AddressSize], addr.AsSlice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetHopLimit(v uint8) {
|
||||||
|
b[hopLimit] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetNextHeader(v byte) {
|
||||||
|
b[IPv6NextHeaderOffset] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) SetProtocol(p IPProtocol) {
|
||||||
|
b.SetNextHeader(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) DecTimeToLive() {
|
||||||
|
b[hopLimit] = b[hopLimit] - uint8(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IPv6Packet) SetChecksum(uint16) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IPv6Packet) ResetChecksum() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) PseudoSum() uint32 {
|
||||||
|
sum := Sum(b[v6SrcAddr:IPv6FixedHeaderSize])
|
||||||
|
sum += uint32(b.Protocol())
|
||||||
|
sum += uint32(b.PayloadLength())
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b IPv6Packet) Valid() bool {
|
||||||
|
return len(b) >= IPv6MinimumSize && len(b) >= int(b.PayloadLength())+IPv6MinimumSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func IPVersion(b []byte) int {
|
||||||
|
if len(b) < versIHL+1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return int(b[versIHL] >> ipVersionShift)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ IP = (*IPv6Packet)(nil)
|
90
listener/tun/ipstack/system/mars/tcpip/tcp.go
Normal file
90
listener/tun/ipstack/system/mars/tcpip/tcp.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package tcpip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TCPFin uint16 = 1 << 0
|
||||||
|
TCPSyn uint16 = 1 << 1
|
||||||
|
TCPRst uint16 = 1 << 2
|
||||||
|
TCPPuh uint16 = 1 << 3
|
||||||
|
TCPAck uint16 = 1 << 4
|
||||||
|
TCPUrg uint16 = 1 << 5
|
||||||
|
TCPEce uint16 = 1 << 6
|
||||||
|
TCPEwr uint16 = 1 << 7
|
||||||
|
TCPNs uint16 = 1 << 8
|
||||||
|
)
|
||||||
|
|
||||||
|
const TCPHeaderSize = 20
|
||||||
|
|
||||||
|
type TCPPacket []byte
|
||||||
|
|
||||||
|
func (p TCPPacket) SourcePort() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) SetSourcePort(port uint16) {
|
||||||
|
binary.BigEndian.PutUint16(p, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) DestinationPort() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) SetDestinationPort(port uint16) {
|
||||||
|
binary.BigEndian.PutUint16(p[2:], port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) Flags() uint16 {
|
||||||
|
return uint16(p[13] | (p[12] & 0x1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) Checksum() uint16 {
|
||||||
|
return binary.BigEndian.Uint16(p[16:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) SetChecksum(sum [2]byte) {
|
||||||
|
p[16] = sum[0]
|
||||||
|
p[17] = sum[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) ResetChecksum(psum uint32) {
|
||||||
|
p.SetChecksum(zeroChecksum)
|
||||||
|
p.SetChecksum(Checksum(psum, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) Valid() bool {
|
||||||
|
return len(p) >= TCPHeaderSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p TCPPacket) Verify(sourceAddress net.IP, targetAddress net.IP) error {
|
||||||
|
var checksum [2]byte
|
||||||
|
checksum[0] = p[16]
|
||||||
|
checksum[1] = p[17]
|
||||||
|
|
||||||
|
// reset checksum
|
||||||
|
p[16] = 0
|
||||||
|
p[17] = 0
|
||||||
|
|
||||||
|
// restore checksum
|
||||||
|
defer func() {
|
||||||
|
p[16] = checksum[0]
|
||||||
|
p[17] = checksum[1]
|
||||||
|
}()
|
||||||
|
|
||||||
|
// check checksum
|
||||||
|
s := uint32(0)
|
||||||
|
s += Sum(sourceAddress)
|
||||||
|
s += Sum(targetAddress)
|
||||||
|
s += uint32(TCP)
|
||||||
|
s += uint32(len(p))
|
||||||
|
|
||||||
|
check := Checksum(s, p)
|
||||||
|
if checksum[0] != check[0] || checksum[1] != check[1] {
|
||||||
|
return ErrInvalidChecksum
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user