mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 11:42:43 +08:00
Chore: refactor TUN
This commit is contained in:
parent
e9efa27589
commit
0175b103d0
4
Makefile
4
Makefile
|
@ -14,7 +14,7 @@ PLATFORM_LIST = \
|
|||
linux-armv5 \
|
||||
linux-armv6 \
|
||||
linux-armv7 \
|
||||
linux-armv8 \
|
||||
linux-arm64 \
|
||||
linux-mips-softfloat \
|
||||
linux-mips-hardfloat \
|
||||
linux-mipsle-softfloat \
|
||||
|
@ -57,7 +57,7 @@ linux-armv6:
|
|||
linux-armv7:
|
||||
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-armv8:
|
||||
linux-arm64:
|
||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips-softfloat:
|
||||
|
|
12
README.md
12
README.md
|
@ -60,7 +60,7 @@ Support `geosite` with `fallback-filter`.
|
|||
fallback-filter:
|
||||
geoip: false
|
||||
geosite:
|
||||
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
|
||||
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to untrusted DNS providers.
|
||||
domain:
|
||||
- +.example.com
|
||||
ipcidr:
|
||||
|
@ -70,13 +70,15 @@ Support `geosite` with `fallback-filter`.
|
|||
### TUN configuration
|
||||
Supports macOS, Linux and Windows.
|
||||
|
||||
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into Clash home directory.
|
||||
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into the system32 directory.
|
||||
```yaml
|
||||
# Enable the TUN listener
|
||||
tun:
|
||||
enable: true
|
||||
stack: gvisor # system or gvisor
|
||||
dns-listen: 0.0.0.0:53 # additional dns server listen on TUN
|
||||
stack: gvisor # System or gVisor
|
||||
# device: tun://utun8 # or fd://xxx, it's optional
|
||||
dns-hijack:
|
||||
- 0.0.0.0:53 # hijack all
|
||||
auto-route: true # auto set global route
|
||||
```
|
||||
### Rules configuration
|
||||
|
@ -114,7 +116,7 @@ rules:
|
|||
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
|
||||
|
||||
- GEOIP,telegram,PROXY,no-resolve
|
||||
- GEOIP,private,DIRECT,no-resolve
|
||||
- GEOIP,lan,DIRECT,no-resolve
|
||||
- GEOIP,cn,DIRECT
|
||||
|
||||
- MATCH,PROXY
|
||||
|
|
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)
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
C "github.com/Dreamacro/clash/constant"
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
@ -46,7 +48,6 @@ type Inbound struct {
|
|||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Tun Tun `json:"tun"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
|
@ -91,10 +92,11 @@ type Profile struct {
|
|||
|
||||
// Tun config
|
||||
type Tun struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Stack string `yaml:"stack" json:"stack"`
|
||||
DNSListen string `yaml:"dns-listen" json:"dns-listen"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Device string `yaml:"device" json:"device"`
|
||||
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
||||
DNSHijack []net.IP `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
|
@ -137,6 +139,14 @@ type RawFallbackFilter struct {
|
|||
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 {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
|
@ -158,7 +168,7 @@ type RawConfig struct {
|
|||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Tun Tun `yaml:"tun"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||
|
@ -188,16 +198,18 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||
Rule: []string{},
|
||||
Proxy: []map[string]interface{}{},
|
||||
ProxyGroup: []map[string]interface{}{},
|
||||
Tun: Tun{
|
||||
Tun: RawTun{
|
||||
Enable: false,
|
||||
Stack: "system",
|
||||
DNSListen: "0.0.0.0:53",
|
||||
Device: "",
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||
AutoRoute: true,
|
||||
},
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
UseHosts: true,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
Enable: false,
|
||||
UseHosts: true,
|
||||
EnhancedMode: C.DNSMapping,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
FallbackFilter: RawFallbackFilter{
|
||||
GeoIP: true,
|
||||
GeoIPCode: "CN",
|
||||
|
@ -208,6 +220,10 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||
"114.114.114.114",
|
||||
"223.5.5.5",
|
||||
},
|
||||
NameServer: []string{ // default if user not set
|
||||
"https://doh.pub/dns-query",
|
||||
"tls://223.5.5.5:853",
|
||||
},
|
||||
},
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
|
@ -233,6 +249,14 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||
}
|
||||
config.General = general
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -282,7 +306,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||
RedirPort: cfg.RedirPort,
|
||||
TProxyPort: cfg.TProxyPort,
|
||||
MixedPort: cfg.MixedPort,
|
||||
Tun: cfg.Tun,
|
||||
AllowLan: cfg.AllowLan,
|
||||
BindAddress: cfg.BindAddress,
|
||||
},
|
||||
|
@ -722,3 +745,37 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
|||
}
|
||||
return users
|
||||
}
|
||||
|
||||
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 []net.IP
|
||||
for _, dns := range rawTun.DNSHijack {
|
||||
host, _, err := net.SplitHostPort(dns)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dns-hijack error: %w", err)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("dns-hijack error: invaid host format")
|
||||
}
|
||||
|
||||
dnsHijack = append(dnsHijack, ip)
|
||||
}
|
||||
|
||||
return &Tun{
|
||||
Enable: rawTun.Enable,
|
||||
Device: rawTun.Device,
|
||||
Stack: rawTun.Stack,
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: rawTun.AutoRoute,
|
||||
}, nil
|
||||
}
|
||||
|
|
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(interface{}) 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() (interface{}, 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"
|
||||
}
|
||||
}
|
12
go.mod
12
go.mod
|
@ -4,13 +4,13 @@ go 1.17
|
|||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
||||
github.com/Kr328/tun2socket v0.0.0-20211231120722-962f339492e8
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.0
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v4.2.0+incompatible
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489
|
||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99
|
||||
github.com/miekg/dns v1.1.46
|
||||
github.com/oschwald/geoip2-golang v1.6.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
|
@ -23,13 +23,15 @@ require (
|
|||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gvisor.dev/gvisor v0.0.0-20220302074230-2966fe9e53f9
|
||||
gvisor.dev/gvisor v0.0.0-20220308041304-8e3b9b36c99e
|
||||
)
|
||||
|
||||
replace github.com/kr328/tun2socket => github.com/yaling888/tun2socket v0.0.0-20220222131921-1354a6a41c94
|
||||
replace github.com/Kr328/tun2socket => github.com/yaling888/tun2socket v0.0.0-20220308132430-288573ddac86
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
@ -39,9 +41,9 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
|
21
go.sum
21
go.sum
|
@ -373,8 +373,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
|
|||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU=
|
||||
github.com/yaling888/tun2socket v0.0.0-20220222131921-1354a6a41c94 h1:+8PkDs3mqiFRlpRpUdU0eF1iyacO5IfrhHnwieWXXIo=
|
||||
github.com/yaling888/tun2socket v0.0.0-20220222131921-1354a6a41c94/go.mod h1:CKx8EO2u1jWiDY1pndf6JIrgEMZqKlSjpgudni/d0UM=
|
||||
github.com/yaling888/tun2socket v0.0.0-20220308132430-288573ddac86 h1:tsdyvi2vkj931IlH5qjcc3V6df/P/V+sBW4Kg5h/8MA=
|
||||
github.com/yaling888/tun2socket v0.0.0-20220308132430-288573ddac86/go.mod h1:YR9wK13TgI5ww8iKWm91MHiSoHC7Oz0U4beCCmtXqLw=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -410,6 +410,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -490,6 +491,7 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
|
@ -575,6 +577,9 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -589,8 +594,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0=
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
|
@ -651,6 +657,11 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 h1:Nrf94TOjrvW8nm6N3u2xtbnMZaZudNI9b8nIJH8p8qY=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1 h1:OnYw96PF+CsIMrqWo5QP3Q59q5hY1rFErk/yN3cS+JQ=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1/go.mod h1:EApyTk/ZNrkbZjurHL1nleDYnsPpJYBO7LZEBCyDAHk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
|
@ -760,8 +771,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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gvisor.dev/gvisor v0.0.0-20220302074230-2966fe9e53f9 h1:nqrG1uWqWMYbSs2iSnUjRhL/sK9O78Fa170Dmv5F9Ng=
|
||||
gvisor.dev/gvisor v0.0.0-20220302074230-2966fe9e53f9/go.mod h1:V4WNP2Uwtx69eOhvLDSQ734EaTJTaBI3P8KgRAlROsg=
|
||||
gvisor.dev/gvisor v0.0.0-20220308041304-8e3b9b36c99e h1:vG9ldlsp4kcxnVLJ4+KkqZJE3XsZI4KmGX1w78hgPwU=
|
||||
gvisor.dev/gvisor v0.0.0-20220308041304-8e3b9b36c99e/go.mod h1:V4WNP2Uwtx69eOhvLDSQ734EaTJTaBI3P8KgRAlROsg=
|
||||
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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
P "github.com/Dreamacro/clash/listener"
|
||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
"github.com/Dreamacro/clash/listener/tun/dev"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
@ -79,9 +78,10 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||
updateRules(cfg.Rules)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateProfile(cfg)
|
||||
updateIPTables(cfg.DNS, cfg.General)
|
||||
updateDNS(cfg.DNS, cfg.General)
|
||||
updateDNS(cfg.DNS, cfg.Tun)
|
||||
updateGeneral(cfg.General, force)
|
||||
updateIPTables(cfg.DNS, cfg.General.TProxyPort, cfg.General.Interface, cfg.Tun.Enable)
|
||||
updateTun(cfg.Tun)
|
||||
updateExperimental(cfg)
|
||||
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
|
@ -101,7 +101,6 @@ func GetGeneral() *config.General {
|
|||
RedirPort: ports.RedirPort,
|
||||
TProxyPort: ports.TProxyPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Tun: P.Tun(),
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
BindAddress: P.BindAddress(),
|
||||
|
@ -116,15 +115,7 @@ func GetGeneral() *config.General {
|
|||
|
||||
func updateExperimental(c *config.Config) {}
|
||||
|
||||
func updateDNS(c *config.DNS, general *config.General) {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
resolver.MainResolver = nil
|
||||
resolver.DefaultHostMapper = nil
|
||||
dns.ReCreateServer("", nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS, t *config.Tun) {
|
||||
cfg := dns.Config{
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
|
@ -155,9 +146,22 @@ func updateDNS(c *config.DNS, general *config.General) {
|
|||
resolver.DefaultResolver = r
|
||||
resolver.MainResolver = mr
|
||||
resolver.DefaultHostMapper = m
|
||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
||||
|
||||
dns.ReCreateServer(c.Listen, r, m)
|
||||
if t.Enable {
|
||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
||||
}
|
||||
|
||||
if c.Enable {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func updateHosts(tree *trie.DomainTrie) {
|
||||
|
@ -172,29 +176,24 @@ func updateRules(rules []C.Rule) {
|
|||
tunnel.UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateTun(tun *config.Tun) {
|
||||
P.ReCreateTun(tun, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
tunnel.SetMode(general.Mode)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
|
||||
if (general.Tun.Enable || general.TProxyPort != 0) && general.Interface == "" {
|
||||
autoDetectInterfaceName, err := dev.GetAutoDetectInterface()
|
||||
if err == nil {
|
||||
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 dialer.DefaultInterface.Load() != "" {
|
||||
log.Infoln("Use interface name: %s", general.Interface)
|
||||
}
|
||||
|
||||
log.Infoln("Use interface name: %s", general.Interface)
|
||||
|
||||
dialer.DefaultInterface.Store(general.Interface)
|
||||
|
||||
if general.RoutingMark > 0 {
|
||||
if general.RoutingMark > 0 || (general.RoutingMark == 0 && general.TProxyPort == 0) {
|
||||
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
||||
if general.RoutingMark > 0 {
|
||||
log.Infoln("Use routing mark: %#x", general.RoutingMark)
|
||||
}
|
||||
}
|
||||
|
||||
iface.FlushCache()
|
||||
|
@ -217,7 +216,6 @@ func updateGeneral(general *config.General, force bool) {
|
|||
P.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
||||
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
||||
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
P.ReCreateTun(general.Tun, tcpIn, udpIn)
|
||||
}
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
|
@ -263,8 +261,28 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
|||
}
|
||||
}
|
||||
|
||||
func updateIPTables(dns *config.DNS, general *config.General) {
|
||||
if runtime.GOOS != "linux" || dns.Listen == "" || general.TProxyPort == 0 || general.Tun.Enable {
|
||||
func updateIPTables(dns *config.DNS, tProxyPort int, interfaceName string, tunEnable bool) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -278,23 +296,14 @@ func updateIPTables(dns *config.DNS, general *config.General) {
|
|||
return
|
||||
}
|
||||
|
||||
tproxy.CleanUpTProxyLinuxIPTables()
|
||||
|
||||
if dialer.DefaultRoutingMark.Load() == 0 {
|
||||
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() {
|
||||
P.Cleanup()
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
tproxy.CleanUpTProxyLinuxIPTables()
|
||||
}
|
||||
|
|
|
@ -78,10 +78,6 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), 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 {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
|
@ -34,7 +33,7 @@ var (
|
|||
tproxyUDPListener *tproxy.UDPListener
|
||||
mixedListener *mixed.Listener
|
||||
mixedUDPLister *socks.UDPListener
|
||||
tunAdapter ipstack.TunAdapter
|
||||
tunStackListener ipstack.Stack
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
|
@ -65,18 +64,6 @@ func SetAllowLan(al bool) {
|
|||
allowLan = al
|
||||
}
|
||||
|
||||
func Tun() config.Tun {
|
||||
if tunAdapter == nil {
|
||||
return config.Tun{}
|
||||
}
|
||||
return config.Tun{
|
||||
Enable: true,
|
||||
Stack: tunAdapter.Stack(),
|
||||
DNSListen: tunAdapter.DNSListen(),
|
||||
AutoRoute: tunAdapter.AutoRoute(),
|
||||
}
|
||||
}
|
||||
|
||||
func SetBindAddress(host string) {
|
||||
bindAddress = host
|
||||
}
|
||||
|
@ -320,30 +307,30 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||
}
|
||||
|
||||
func ReCreateTun(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
tunMux.Lock()
|
||||
defer tunMux.Unlock()
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorln("Start TUN interface error: %s", err.Error())
|
||||
log.Errorln("Start TUN listening error: %s", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
}()
|
||||
|
||||
if tunAdapter != nil {
|
||||
tunAdapter.Close()
|
||||
tunAdapter = nil
|
||||
if tunStackListener != nil {
|
||||
tunStackListener.Close()
|
||||
tunStackListener = nil
|
||||
}
|
||||
|
||||
if !conf.Enable {
|
||||
if !tunConf.Enable {
|
||||
return
|
||||
}
|
||||
|
||||
tunAdapter, err = tun.New(conf, tcpIn, udpIn)
|
||||
tunStackListener, err = tun.New(tunConf, tcpIn, udpIn)
|
||||
if err != nil {
|
||||
log.Warnln("Failed to start TUN interface: %s", err.Error())
|
||||
log.Warnln("Failed to start TUN listening: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,12 +389,3 @@ func genAddr(host string, port int, allowLan bool) string {
|
|||
|
||||
return fmt.Sprintf("127.0.0.1:%d", port)
|
||||
}
|
||||
|
||||
// Cleanup clean up something
|
||||
func Cleanup() {
|
||||
if runtime.GOOS == "windows" {
|
||||
if tunAdapter != nil {
|
||||
tunAdapter.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,17 @@ package tproxy
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
interfaceName = ""
|
||||
tproxyPort = 0
|
||||
dnsPort = 0
|
||||
tProxyPort = 0
|
||||
interfaceName = ""
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -24,7 +23,7 @@ const (
|
|||
|
||||
func SetTProxyLinuxIPTables(ifname string, tport int, dport int) 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)
|
||||
}
|
||||
|
||||
|
@ -33,7 +32,7 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
|||
}
|
||||
|
||||
interfaceName = ifname
|
||||
tproxyPort = tport
|
||||
tProxyPort = tport
|
||||
dnsPort = dport
|
||||
|
||||
// add route
|
||||
|
@ -63,8 +62,8 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error {
|
|||
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 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 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 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("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))
|
||||
|
@ -96,12 +95,12 @@ 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 udp --dport 53 -j clash_dns_output")
|
||||
|
||||
log.Infoln("[TProxy] Setting iptables completed")
|
||||
log.Infoln("[TPROXY] Setting iptables completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanUpTProxyLinuxIPTables() {
|
||||
if interfaceName == "" || tproxyPort == 0 || dnsPort == 0 {
|
||||
if interfaceName == "" || tProxyPort == 0 || dnsPort == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -111,7 +110,7 @@ func CleanUpTProxyLinuxIPTables() {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -167,16 +166,13 @@ func addLocalnetworkToChain(chain string) {
|
|||
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 255.255.255.255/32 -j RETURN", chain))
|
||||
}
|
||||
|
||||
func execCmd(cmdstr string) (string, error) {
|
||||
log.Debugln("[TProxy] %s", cmdstr)
|
||||
func execCmd(cmdStr string) error {
|
||||
log.Debugln("[TPROXY] %s", cmdStr)
|
||||
|
||||
args := strings.Split(cmdstr, " ")
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
_, err := cmd.ExecCmd(cmdStr)
|
||||
if err != nil {
|
||||
log.Errorln("[TProxy] error: %s, %s", err.Error(), string(out))
|
||||
return "", err
|
||||
log.Warnln("[TPROXY] exec cmd: %v", err)
|
||||
}
|
||||
|
||||
return string(out), nil
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func SetLinuxAutoRoute() {
|
||||
log.Infoln("Tun adapter auto setting global route")
|
||||
addLinuxSystemRoute("1")
|
||||
addLinuxSystemRoute("2/7")
|
||||
addLinuxSystemRoute("4/6")
|
||||
addLinuxSystemRoute("8/5")
|
||||
addLinuxSystemRoute("16/4")
|
||||
addLinuxSystemRoute("32/3")
|
||||
addLinuxSystemRoute("64/2")
|
||||
addLinuxSystemRoute("128.0/1")
|
||||
addLinuxSystemRoute("198.18.0/16")
|
||||
}
|
||||
|
||||
func RemoveLinuxAutoRoute() {
|
||||
log.Infoln("Tun adapter removing global route")
|
||||
delLinuxSystemRoute("1")
|
||||
delLinuxSystemRoute("2/7")
|
||||
delLinuxSystemRoute("4/6")
|
||||
delLinuxSystemRoute("8/5")
|
||||
delLinuxSystemRoute("16/4")
|
||||
delLinuxSystemRoute("32/3")
|
||||
delLinuxSystemRoute("64/2")
|
||||
delLinuxSystemRoute("128.0/1")
|
||||
delLinuxSystemRoute("198.18.0/16")
|
||||
}
|
||||
|
||||
func addLinuxSystemRoute(net string) {
|
||||
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command("route", "add", "-net", net, "198.18.0.1")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Errorln("[auto route] Failed to add system route: %s, cmd: %s", err.Error(), cmd.String())
|
||||
}
|
||||
}
|
||||
|
||||
func delLinuxSystemRoute(net string) {
|
||||
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command("route", "delete", "-net", net, "198.18.0.1")
|
||||
_ = cmd.Run()
|
||||
//if err := cmd.Run(); err != nil {
|
||||
// log.Errorln("[auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String())
|
||||
//}
|
||||
}
|
|
@ -1,494 +0,0 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package dev
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
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 {
|
||||
SetLinuxAutoRoute()
|
||||
}
|
||||
|
||||
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 {
|
||||
RemoveLinuxAutoRoute()
|
||||
}
|
||||
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("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if out.Len() == 0 {
|
||||
return "", errors.New("interface not found by default route")
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
|
@ -1,255 +0,0 @@
|
|||
//go:build linux || android
|
||||
// +build linux android
|
||||
|
||||
package dev
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"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://clash0")
|
||||
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
|
||||
}
|
||||
if autoRoute {
|
||||
SetLinuxAutoRoute()
|
||||
}
|
||||
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 {
|
||||
SetLinuxAutoRoute()
|
||||
}
|
||||
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() {
|
||||
if t.autoRoute {
|
||||
RemoveLinuxAutoRoute()
|
||||
}
|
||||
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) 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
|
||||
}
|
|
@ -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,162 +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 = "Clash"
|
||||
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,130 +0,0 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
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 windows.Handle
|
||||
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 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
//const (
|
||||
// LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
|
||||
// LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
|
||||
//)
|
||||
//module, err := windows.LoadLibraryEx(d.Name, 0, LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
module, err := windows.LoadLibraryEx(C.Path.GetAssetLocation(d.Name), 0, windows.LOAD_WITH_ALTERED_SEARCH_PATH)
|
||||
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 windows.GetProcAddress(p.dll.module, p.Name)
|
||||
}
|
||||
|
||||
// Version returns the version of the Wintun DLL.
|
||||
func Version() string {
|
||||
if modwintun.Load() != nil {
|
||||
return "unknown"
|
||||
}
|
||||
resInfo, err := windows.FindResource(modwintun.module, windows.ResourceID(1), windows.RT_VERSION)
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
data, err := windows.LoadResourceData(modwintun.module, resInfo)
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
var fixedInfo *windows.VS_FIXEDFILEINFO
|
||||
fixedInfoLen := uint32(unsafe.Sizeof(*fixedInfo))
|
||||
err = windows.VerQueryValue(unsafe.Pointer(&data[0]), `\`, unsafe.Pointer(&fixedInfo), &fixedInfoLen)
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
version := fmt.Sprintf("%d.%d", (fixedInfo.FileVersionMS>>16)&0xff, (fixedInfo.FileVersionMS>>0)&0xff)
|
||||
if nextNibble := (fixedInfo.FileVersionLS >> 16) & 0xff; nextNibble != 0 {
|
||||
version += fmt.Sprintf(".%d", nextNibble)
|
||||
}
|
||||
if nextNibble := (fixedInfo.FileVersionLS >> 0) & 0xff; nextNibble != 0 {
|
||||
version += fmt.Sprintf(".%d", nextNibble)
|
||||
}
|
||||
return version
|
||||
}
|
|
@ -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()
|
||||
}
|
109
listener/tun/device/tun/tun_wireguard.go
Normal file
109
listener/tun/device/tun/tun_wireguard.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
//go:build !linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"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 := make([]byte, t.offset+int(t.mtu))
|
||||
|
||||
n, err := t.nt.Read(buff, t.offset)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
package commons
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,7 +1,7 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package wintun
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,11 +1,30 @@
|
|||
package commons
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const DefaultDnsReadTimeout = time.Second * 10
|
||||
|
||||
func ShouldHijackDns(dnsAdds []net.IP, targetAddr net.IP, targetPort int) bool {
|
||||
if targetPort != 53 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, ip := range dnsAdds {
|
||||
if ip.IsUnspecified() || ip.Equal(targetAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
|
|
16
listener/tun/ipstack/commons/router.go
Normal file
16
listener/tun/ipstack/commons/router.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
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(m net.IPMask) string {
|
||||
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])
|
||||
}
|
56
listener/tun/ipstack/commons/router_darwin.go
Normal file
56
listener/tun/ipstack/commons/router_darwin.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"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 *net.IPNet, forceMTU int, autoRoute bool) error {
|
||||
interfaceName := dev.Name()
|
||||
if addr.IP.To4() == nil {
|
||||
return fmt.Errorf("supported ipv4 only")
|
||||
}
|
||||
|
||||
ip := addr.IP.String()
|
||||
netmask := IPv4MaskString(addr.Mask)
|
||||
cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, ip)
|
||||
|
||||
_, 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 *net.IPNet) 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
47
listener/tun/ipstack/commons/router_linux.go
Normal file
47
listener/tun/ipstack/commons/router_linux.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"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 *net.IPNet, forceMTU int, autoRoute bool) error {
|
||||
interfaceName := dev.Name()
|
||||
_, err := cmd.ExecCmd(fmt.Sprintf("ip addr add %s dev %s", addr.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 *net.IPNet) error {
|
||||
for _, route := range ROUTES {
|
||||
if err := execRouterCmd("add", route, interfaceName, addr.IP.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"
|
||||
"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(dev device.Device, addr *net.IPNet, forceMTU int, autoRoute bool) error {
|
||||
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dev
|
||||
package commons
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -9,128 +6,27 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/dev/wintun"
|
||||
"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/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}")
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
||||
if err == nil {
|
||||
WintunStaticRequestedGUID = &requestedGUID
|
||||
log.Debugln("Generate GUID: %s", WintunStaticRequestedGUID.String())
|
||||
} else {
|
||||
log.Warnln("Error parese GUID from string: %v", err)
|
||||
return ifname, err
|
||||
}
|
||||
|
||||
interfaceName := "Clash"
|
||||
mtu := 9000
|
||||
|
||||
tun, err := CreateTUN(interfaceName, mtu, tunAddress, autoRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tun, nil
|
||||
return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6))
|
||||
}
|
||||
|
||||
//
|
||||
// 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()
|
||||
func ConfigInterfaceAddress(dev device.Device, addr *net.IPNet, forceMTU int, autoRoute bool) error {
|
||||
retryOnFailure := StartedAtBoot()
|
||||
tryTimes := 0
|
||||
startOver:
|
||||
var err error
|
||||
|
@ -141,20 +37,22 @@ startOver:
|
|||
}
|
||||
tryTimes++
|
||||
|
||||
luid := winipcfg.LUID(tun.LUID())
|
||||
log.Infoln("[wintun]: tun adapter LUID: %d", luid)
|
||||
mtu, err := tun.MTU()
|
||||
luid := winipcfg.LUID(dev.(*tun.TUN).LUID())
|
||||
|
||||
if err != nil {
|
||||
return errors.New("unable to get device mtu")
|
||||
if guid, err1 := luid.GUID(); err1 == nil {
|
||||
log.Infoln("[wintun]: tun adapter GUID: %s", guid.String())
|
||||
}
|
||||
|
||||
tunAddress := ParseIPCidr(addr.String())
|
||||
addresses := []net.IPNet{tunAddress.IPNet()}
|
||||
|
||||
family := winipcfg.AddressFamily(windows.AF_INET)
|
||||
familyV6 := winipcfg.AddressFamily(windows.AF_INET6)
|
||||
|
||||
tunAddress := wintun.ParseIPCidr(tun.tunAddress + "/16")
|
||||
|
||||
addresses := []net.IPNet{tunAddress.IPNet()}
|
||||
currentFamily := winipcfg.AddressFamily(windows.AF_INET6)
|
||||
if addr.IP.To4() != nil {
|
||||
currentFamily = winipcfg.AddressFamily(windows.AF_INET)
|
||||
}
|
||||
|
||||
err = luid.FlushIPAddresses(familyV6)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
|
@ -184,19 +82,12 @@ startOver:
|
|||
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"),
|
||||
if autoRoute {
|
||||
allowedIPs := []*IPCidr{}
|
||||
routeArr := append(ROUTES, "224.0.0.0/4", "255.255.255.255/32")
|
||||
|
||||
for _, route := range routeArr {
|
||||
allowedIPs = append(allowedIPs, ParseIPCidr(route))
|
||||
}
|
||||
|
||||
estimatedRouteCount := len(allowedIPs)
|
||||
|
@ -260,10 +151,10 @@ startOver:
|
|||
}
|
||||
}
|
||||
|
||||
err = luid.SetIPAddressesForFamily(family, addresses)
|
||||
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
||||
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
||||
cleanupAddressesOnDisconnectedInterfaces(family, addresses)
|
||||
err = luid.SetIPAddressesForFamily(family, addresses)
|
||||
cleanupAddressesOnDisconnectedInterfaces(currentFamily, addresses)
|
||||
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
||||
}
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
|
@ -280,10 +171,10 @@ startOver:
|
|||
ipif.DadTransmits = 0
|
||||
ipif.ManagedAddressConfigurationSupported = false
|
||||
ipif.OtherStatefulConfigurationSupported = false
|
||||
if mtu > 0 {
|
||||
ipif.NLMTU = uint32(mtu)
|
||||
if forceMTU > 0 {
|
||||
ipif.NLMTU = uint32(forceMTU)
|
||||
}
|
||||
if (family == windows.AF_INET && foundDefault4) || (family == windows.AF_INET6 && foundDefault6) {
|
||||
if foundDefault4 {
|
||||
ipif.UseAutomaticMetric = false
|
||||
ipif.Metric = 0
|
||||
}
|
||||
|
@ -303,7 +194,13 @@ startOver:
|
|||
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
|
||||
|
@ -353,16 +250,6 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
|||
}
|
||||
}
|
||||
|
||||
// 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 {
|
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)
|
||||
}
|
125
listener/tun/ipstack/gvisor/handler.go
Normal file
125
listener/tun/ipstack/gvisor/handler.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package gvisor
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"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 []net.IP
|
||||
|
||||
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: "",
|
||||
}
|
||||
|
||||
if D.ShouldHijackDns(gh.DNSAdds, rAddr.IP, rAddr.Port) {
|
||||
go func() {
|
||||
log.Debugln("[TUN] hijack dns tcp: %s", rAddr.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 {
|
||||
return
|
||||
}
|
||||
|
||||
if int(length) > len(buf) {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := tunConn.Read(buf[:length])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := D.RelayDnsPacket(buf[:n])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = 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: "",
|
||||
}
|
||||
|
||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
|
||||
n, addr, err := tunConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
pool.Put(buf)
|
||||
return
|
||||
}
|
||||
|
||||
payload := buf[:n]
|
||||
|
||||
if D.ShouldHijackDns(gh.DNSAdds, rAddr.IP, rAddr.Port) {
|
||||
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,
|
||||
}
|
||||
|
||||
gh.UDPIn <- inbound.NewPacket(target, gvPacket, C.TUN)
|
||||
}
|
||||
}()
|
||||
}
|
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,274 +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, tunAddress string, 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
|
||||
}
|
||||
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.DNSListen)
|
||||
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() {
|
||||
if t.dnsserver != nil {
|
||||
t.dnsserver.Stop()
|
||||
}
|
||||
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 err != nil && !t.device.IsClose() {
|
||||
log.Errorln("can not read from tun: %v", err)
|
||||
continue
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
var p tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(packet) {
|
||||
case header.IPv4Version:
|
||||
p = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
p = header.IPv6ProtocolNumber
|
||||
}
|
||||
if linkEP.IsAttached() {
|
||||
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Data: buffer.View(packet[:n]).ToVectorisedView(),
|
||||
})
|
||||
linkEP.InjectInbound(p, packetBuffer)
|
||||
|
||||
packetBuffer.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,292 +0,0 @@
|
|||
package gvisor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/adapters/gonet"
|
||||
"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())
|
||||
)
|
||||
|
||||
// DNSServer is DNS Server listening on tun devcice
|
||||
type DNSServer struct {
|
||||
*dns.Server
|
||||
resolver *dns.Resolver
|
||||
|
||||
stack *stack.Stack
|
||||
tcpListener net.Listener
|
||||
udpEndpoint *dnsEndpoint
|
||||
udpEndpointID *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}
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
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, ip net.IP, port int, nicID tcpip.NICID) (*DNSServer, error) {
|
||||
var v4 bool
|
||||
var err error
|
||||
|
||||
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
||||
var protocol tcpip.NetworkProtocolNumber
|
||||
if ip.To4() != nil {
|
||||
v4 = true
|
||||
address.Addr = tcpip.Address(ip.To4())
|
||||
protocol = ipv4.ProtocolNumber
|
||||
|
||||
} else {
|
||||
v4 = false
|
||||
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 = ""
|
||||
}
|
||||
|
||||
handler := dns.NewHandler(resolver, mapper)
|
||||
serverIn := &dns.Server{}
|
||||
serverIn.SetHandler(handler)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// TCP DNS
|
||||
var tcpListener net.Listener
|
||||
if v4 {
|
||||
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
|
||||
} else {
|
||||
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not listen on tun: %v", err)
|
||||
}
|
||||
|
||||
server := &DNSServer{
|
||||
Server: serverIn,
|
||||
resolver: resolver,
|
||||
stack: s,
|
||||
tcpListener: tcpListener,
|
||||
udpEndpoint: endpoint,
|
||||
udpEndpointID: id,
|
||||
NICID: nicID,
|
||||
}
|
||||
server.SetHandler(handler)
|
||||
server.Server.Server = &D.Server{Listener: tcpListener, Handler: server}
|
||||
|
||||
go func() {
|
||||
server.ActivateAndServe()
|
||||
}()
|
||||
|
||||
return server, err
|
||||
}
|
||||
|
||||
// Stop stop the DNS Server on tun
|
||||
func (s *DNSServer) Stop() {
|
||||
// shutdown TCP DNS Server
|
||||
s.Server.Shutdown()
|
||||
// remove TCP endpoint from stack
|
||||
if s.Listener != nil {
|
||||
s.Listener.Close()
|
||||
}
|
||||
// remove udp endpoint from stack
|
||||
s.stack.UnregisterTransportEndpoint(
|
||||
[]tcpip.NetworkProtocolNumber{
|
||||
ipv4.ProtocolNumber,
|
||||
ipv6.ProtocolNumber,
|
||||
},
|
||||
udp.ProtocolNumber,
|
||||
*s.udpEndpointID,
|
||||
s.udpEndpoint,
|
||||
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
|
||||
s.NICID)
|
||||
}
|
||||
|
||||
// DNSListen return the listening address of DNS Server
|
||||
func (t *gvisorAdapter) DNSListen() string {
|
||||
if t.dnsserver != nil {
|
||||
id := t.dnsserver.udpEndpointID
|
||||
return fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Stop stop the DNS Server on tun
|
||||
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addr string) error {
|
||||
if addr == "" && t.dnsserver == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr == t.DNSListen() && t.dnsserver != nil && t.dnsserver.resolver == resolver {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.dnsserver != nil {
|
||||
t.dnsserver.Stop()
|
||||
t.dnsserver = nil
|
||||
log.Debugln("tun DNS server stoped")
|
||||
}
|
||||
|
||||
var err error
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
if port == "0" || port == "" || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if resolver == nil {
|
||||
return fmt.Errorf("failed to create DNS server on tun: resolver not provided")
|
||||
}
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.dnsserver = server
|
||||
log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled())
|
||||
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,109 +0,0 @@
|
|||
package gvisor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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)
|
||||
}
|
||||
|
||||
r, _ := c.s.FindRoute(c.pkt.NICID, localAddress, c.id.RemoteAddress, c.pkt.NetworkProtocolNumber, false /* multicastLoop */)
|
||||
return writeUDP(r, data, localPort, c.id.RemotePort)
|
||||
}
|
||||
|
||||
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
|
||||
DNSListen() string
|
||||
AutoRoute() bool
|
||||
}
|
|
@ -1,76 +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 * 10
|
||||
|
||||
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...)
|
||||
}
|
185
listener/tun/ipstack/system/stack.go
Normal file
185
listener/tun/ipstack/system/stack.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/Kr328/tun2socket"
|
||||
)
|
||||
|
||||
type sysStack struct {
|
||||
stack io.Closer
|
||||
device device.Device
|
||||
}
|
||||
|
||||
func (s sysStack) Close() error {
|
||||
if s.stack != nil {
|
||||
_ = s.stack.Close()
|
||||
}
|
||||
if s.device != nil {
|
||||
_ = s.device.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _, ipv4LoopBack, _ = net.ParseCIDR("127.0.0.0/8")
|
||||
|
||||
func New(device device.Device, dnsHijack []net.IP, portal net.IP, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
||||
gateway := portal
|
||||
|
||||
stack, err := tun2socket.StartTun2Socket(device, gateway, portal)
|
||||
if err != nil {
|
||||
_ = device.Close()
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsAddr := dnsHijack
|
||||
|
||||
tcp := func() {
|
||||
defer stack.TCP().Close()
|
||||
defer log.Debugln("TCP: closed")
|
||||
|
||||
for stack.TCP().SetDeadline(time.Time{}) == nil {
|
||||
conn, err := stack.TCP().Accept()
|
||||
if err != nil {
|
||||
log.Debugln("Accept connection: %v", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
lAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
rAddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
if ipv4LoopBack.Contains(rAddr.IP) {
|
||||
conn.Close()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if D.ShouldHijackDns(dnsAddr, rAddr.IP, rAddr.Port) {
|
||||
go func() {
|
||||
log.Debugln("[TUN] hijack dns tcp: %s", rAddr.String())
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
defer pool.Put(buf)
|
||||
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(C.DefaultTCPTimeout))
|
||||
|
||||
length := uint16(0)
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if int(length) > len(buf) {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := conn.Read(buf[:length])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := D.RelayDnsPacket(buf[:n])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = conn.Write(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.TUN,
|
||||
SrcIP: lAddr.IP,
|
||||
DstIP: rAddr.IP,
|
||||
SrcPort: strconv.Itoa(lAddr.Port),
|
||||
DstPort: strconv.Itoa(rAddr.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
}
|
||||
|
||||
tcpIn <- context.NewConnContext(conn, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
udp := func() {
|
||||
defer stack.UDP().Close()
|
||||
defer log.Debugln("UDP: closed")
|
||||
|
||||
for {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
|
||||
n, lRAddr, rRAddr, err := stack.UDP().ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw := buf[:n]
|
||||
lAddr := lRAddr.(*net.UDPAddr)
|
||||
rAddr := rRAddr.(*net.UDPAddr)
|
||||
|
||||
if ipv4LoopBack.Contains(rAddr.IP) {
|
||||
pool.Put(buf)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if D.ShouldHijackDns(dnsAddr, rAddr.IP, rAddr.Port) {
|
||||
go func() {
|
||||
defer pool.Put(buf)
|
||||
|
||||
msg, err := D.RelayDnsPacket(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
|
||||
|
||||
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
|
||||
}()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
pkt := &packet{
|
||||
local: lAddr,
|
||||
data: raw,
|
||||
writeBack: func(b []byte, addr net.Addr) (int, error) {
|
||||
return stack.UDP().WriteTo(b, addr, lAddr)
|
||||
},
|
||||
drop: func() {
|
||||
pool.Put(buf)
|
||||
},
|
||||
}
|
||||
|
||||
udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN)
|
||||
}
|
||||
}
|
||||
|
||||
go tcp()
|
||||
go udp()
|
||||
go udp()
|
||||
|
||||
return &sysStack{stack: stack, device: device}, nil
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
)
|
||||
|
||||
func handleTCP(conn net.Conn, endpoint *binding.Endpoint, tcpIn chan<- C.ConnContext) {
|
||||
src := &net.TCPAddr{
|
||||
IP: endpoint.Source.IP,
|
||||
Port: int(endpoint.Source.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
dst := &net.TCPAddr{
|
||||
IP: endpoint.Target.IP,
|
||||
Port: int(endpoint.Target.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
addrType := C.AtypIPv4
|
||||
if dst.IP.To4() == nil {
|
||||
addrType = C.AtypIPv6
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.TUN,
|
||||
SrcIP: src.IP,
|
||||
DstIP: dst.IP,
|
||||
SrcPort: strconv.Itoa(src.Port),
|
||||
DstPort: strconv.Itoa(dst.Port),
|
||||
AddrType: addrType,
|
||||
Host: "",
|
||||
}
|
||||
|
||||
//if c, ok := conn.(*net.TCPConn); ok {
|
||||
// c.SetKeepAlive(true)
|
||||
//}
|
||||
tcpIn <- context.NewConnContext(conn, metadata)
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/tun/dev"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/kr328/tun2socket"
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
)
|
||||
|
||||
type systemAdapter struct {
|
||||
device dev.TunDevice
|
||||
tun *tun2socket.Tun2Socket
|
||||
lock sync.Mutex
|
||||
stackName string
|
||||
dnsListen string
|
||||
autoRoute bool
|
||||
}
|
||||
|
||||
func NewAdapter(device dev.TunDevice, conf config.Tun, mtu int, gateway, mirror string, onStop func(), tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
|
||||
adapter := &systemAdapter{
|
||||
device: device,
|
||||
stackName: conf.Stack,
|
||||
dnsListen: conf.DNSListen,
|
||||
autoRoute: conf.AutoRoute,
|
||||
}
|
||||
|
||||
adapter.lock.Lock()
|
||||
defer adapter.lock.Unlock()
|
||||
|
||||
dnsHost, dnsPort, err := net.SplitHostPort(conf.DNSListen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsP, err := strconv.Atoi(dnsPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsAddr := binding.Address{
|
||||
IP: net.ParseIP(dnsHost),
|
||||
Port: uint16(dnsP),
|
||||
}
|
||||
|
||||
t := tun2socket.NewTun2Socket(device, mtu, net.ParseIP(gateway), net.ParseIP(mirror))
|
||||
|
||||
t.SetAllocator(allocUDP)
|
||||
t.SetClosedHandler(onStop)
|
||||
t.SetLogger(&logger{})
|
||||
|
||||
t.SetTCPHandler(func(conn net.Conn, endpoint *binding.Endpoint) {
|
||||
if shouldHijackDns(dnsAddr, endpoint.Target) {
|
||||
hijackTCPDns(conn)
|
||||
log.Debugln("[TUN] hijack dns tcp: %s:%d", endpoint.Target.IP.String(), endpoint.Target.Port)
|
||||
return
|
||||
}
|
||||
|
||||
handleTCP(conn, endpoint, tcpIn)
|
||||
})
|
||||
t.SetUDPHandler(func(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) {
|
||||
if shouldHijackDns(dnsAddr, endpoint.Target) {
|
||||
hijackUDPDns(payload, endpoint, sender)
|
||||
log.Debugln("[TUN] hijack dns udp: %s:%d", endpoint.Target.IP.String(), endpoint.Target.Port)
|
||||
return
|
||||
}
|
||||
|
||||
handleUDP(payload, endpoint, sender, udpIn)
|
||||
})
|
||||
|
||||
t.Start()
|
||||
|
||||
adapter.tun = t
|
||||
|
||||
return adapter, nil
|
||||
}
|
||||
|
||||
func (t *systemAdapter) Stack() string {
|
||||
return t.stackName
|
||||
}
|
||||
|
||||
func (t *systemAdapter) AutoRoute() bool {
|
||||
return t.autoRoute
|
||||
}
|
||||
|
||||
func (t *systemAdapter) DNSListen() string {
|
||||
return t.dnsListen
|
||||
}
|
||||
|
||||
func (t *systemAdapter) Close() {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.stopLocked()
|
||||
}
|
||||
|
||||
func (t *systemAdapter) stopLocked() {
|
||||
if t.tun != nil {
|
||||
t.tun.Close()
|
||||
}
|
||||
|
||||
if t.device != nil {
|
||||
_ = t.device.Close()
|
||||
}
|
||||
|
||||
t.tun = nil
|
||||
t.device = nil
|
||||
}
|
|
@ -1,75 +1,26 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
import "net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
)
|
||||
|
||||
type udpPacket struct {
|
||||
source binding.Address
|
||||
data []byte
|
||||
send redirect.UDPSender
|
||||
type packet struct {
|
||||
local *net.UDPAddr
|
||||
data []byte
|
||||
writeBack func(b []byte, addr net.Addr) (int, error)
|
||||
drop func()
|
||||
}
|
||||
|
||||
func (u *udpPacket) Data() []byte {
|
||||
return u.data
|
||||
func (pkt *packet) Data() []byte {
|
||||
return pkt.data
|
||||
}
|
||||
|
||||
func (u *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
uAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return len(b), u.send(b, &binding.Endpoint{
|
||||
Source: binding.Address{IP: uAddr.IP, Port: uint16(uAddr.Port)},
|
||||
Target: u.source,
|
||||
})
|
||||
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return pkt.writeBack(b, addr)
|
||||
}
|
||||
|
||||
func (u *udpPacket) Drop() {
|
||||
recycleUDP(u.data)
|
||||
func (pkt *packet) Drop() {
|
||||
pkt.drop()
|
||||
}
|
||||
|
||||
func (u *udpPacket) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: u.source.IP,
|
||||
Port: int(u.source.Port),
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func handleUDP(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender, udpIn chan<- *inbound.PacketAdapter) {
|
||||
pkt := &udpPacket{
|
||||
source: endpoint.Source,
|
||||
data: payload,
|
||||
send: sender,
|
||||
}
|
||||
|
||||
rAddr := &net.UDPAddr{
|
||||
IP: endpoint.Target.IP,
|
||||
Port: int(endpoint.Target.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
select {
|
||||
case udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN):
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func allocUDP(size int) []byte {
|
||||
return pool.Get(size)
|
||||
}
|
||||
|
||||
func recycleUDP(payload []byte) {
|
||||
_ = pool.Put(payload)
|
||||
func (pkt *packet) LocalAddr() net.Addr {
|
||||
return pkt.local
|
||||
}
|
||||
|
|
|
@ -1,51 +1,142 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/tun/dev"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/fdbased"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// New create TunAdapter
|
||||
func New(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
|
||||
tunAddress := "198.18.0.1"
|
||||
autoRoute := conf.AutoRoute
|
||||
stack := conf.Stack
|
||||
var tunAdapter ipstack.TunAdapter
|
||||
|
||||
device, err := dev.OpenTunDevice(tunAddress, autoRoute)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open tun: %v", err)
|
||||
// New TunAdapter
|
||||
func New(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
||||
devName := tunConf.Device
|
||||
if devName == "" {
|
||||
devName = generateDeviceName()
|
||||
}
|
||||
|
||||
mtu, err := device.MTU()
|
||||
tunAddrIPNet := &net.IPNet{IP: net.IP{198, 18, 0, 1}, Mask: net.CIDRMask(16, 32)}
|
||||
autoRoute := tunConf.AutoRoute
|
||||
stackType := tunConf.Stack
|
||||
mtu := 9000
|
||||
|
||||
var tunDevice device.Device
|
||||
var tunStack ipstack.Stack
|
||||
|
||||
var err error
|
||||
|
||||
// new tun device
|
||||
tunDevice, err = parseDevice(devName, uint32(mtu))
|
||||
if err != nil {
|
||||
_ = device.Close()
|
||||
return nil, errors.New("unable to get device mtu")
|
||||
return nil, fmt.Errorf("can't open tun: %w", err)
|
||||
}
|
||||
|
||||
if strings.EqualFold(stack, "system") {
|
||||
tunAdapter, err = system.NewAdapter(device, conf, mtu, tunAddress, tunAddress, func() {}, tcpIn, udpIn)
|
||||
} else if strings.EqualFold(stack, "gvisor") {
|
||||
tunAdapter, err = gvisor.NewAdapter(device, conf, tunAddress, tcpIn, udpIn)
|
||||
} else {
|
||||
err = fmt.Errorf("can not support tun ip stack: %s, only support \"system\" and \"gvisor\"", stack)
|
||||
// new ip stack
|
||||
switch stackType {
|
||||
case C.TunGvisor:
|
||||
err = tunDevice.UseEndpoint()
|
||||
if err != nil {
|
||||
_ = tunDevice.Close()
|
||||
return nil, fmt.Errorf("can't attach endpoint to tun: %w", err)
|
||||
}
|
||||
|
||||
tunStack, err = gvisor.New(tunDevice,
|
||||
&gvisor.GVHandler{
|
||||
DNSAdds: tunConf.DNSHijack,
|
||||
TCPIn: tcpIn, UDPIn: udpIn,
|
||||
},
|
||||
gvisor.WithDefault())
|
||||
|
||||
if err != nil {
|
||||
_ = tunDevice.Close()
|
||||
return nil, fmt.Errorf("can't New gvisor stack: %w", err)
|
||||
}
|
||||
case C.TunSystem:
|
||||
err = tunDevice.UseIOBased()
|
||||
if err != nil {
|
||||
_ = tunDevice.Close()
|
||||
return nil, fmt.Errorf("can't attach endpoint to tun: %w", err)
|
||||
}
|
||||
|
||||
tunStack, err = system.New(tunDevice, tunConf.DNSHijack, tunAddrIPNet.IP, tcpIn, udpIn)
|
||||
if err != nil {
|
||||
_ = tunDevice.Close()
|
||||
return nil, fmt.Errorf("can't New system stack: %w", err)
|
||||
}
|
||||
default:
|
||||
// ignore it, should never happen
|
||||
}
|
||||
|
||||
// setting address and routing
|
||||
err = commons.ConfigInterfaceAddress(tunDevice, tunAddrIPNet, mtu, autoRoute)
|
||||
if err != nil {
|
||||
_ = tunDevice.Close()
|
||||
return nil, fmt.Errorf("setting interface address and routing failed: %w", err)
|
||||
}
|
||||
|
||||
setAtLatest(stackType)
|
||||
|
||||
log.Infoln("TUN stack listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", tunDevice.Name(), tunAddrIPNet.IP.String(), mtu, autoRoute, stackType)
|
||||
return tunStack, nil
|
||||
}
|
||||
|
||||
func generateDeviceName() string {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return tun.Driver + "://utun"
|
||||
case "windows":
|
||||
return tun.Driver + "://Clash"
|
||||
default:
|
||||
return tun.Driver + "://clash0"
|
||||
}
|
||||
}
|
||||
|
||||
func parseDevice(s string, mtu uint32) (device.Device, error) {
|
||||
if !strings.Contains(s, "://") {
|
||||
s = fmt.Sprintf("%s://%s", tun.Driver /* default driver */, s)
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
_ = device.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infoln("Tun adapter listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", device.Name(), tunAddress, mtu, autoRoute, stack)
|
||||
return tunAdapter, nil
|
||||
name := u.Host
|
||||
driver := strings.ToLower(u.Scheme)
|
||||
|
||||
switch driver {
|
||||
case fdbased.Driver:
|
||||
return fdbased.Open(name, mtu)
|
||||
case tun.Driver:
|
||||
return tun.Open(name, mtu)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported driver: %s", driver)
|
||||
}
|
||||
}
|
||||
|
||||
func setAtLatest(stackType C.TUNStack) {
|
||||
if stackType != C.TunSystem {
|
||||
return
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
_, _ = cmd.ExecCmd("ipconfig /renew")
|
||||
case "linux":
|
||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1")
|
||||
// _, _ = cmd.ExecCmd("iptables -t filter -P FORWARD ACCEPT")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ replace github.com/Dreamacro/clash => ../
|
|||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
|
||||
github.com/Kr328/tun2socket v0.0.0-20211231120722-962f339492e8 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/containerd/containerd v1.5.8 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
@ -27,7 +28,6 @@ require (
|
|||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20211214070828-5297eed8f489 // indirect
|
||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
|
@ -45,15 +45,17 @@ require (
|
|||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||
google.golang.org/grpc v1.43.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220302074230-2966fe9e53f9 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220308041304-8e3b9b36c99e // indirect
|
||||
)
|
||||
|
|
21
test/go.sum
21
test/go.sum
|
@ -58,6 +58,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
github.com/Kr328/tun2socket v0.0.0-20211231120722-962f339492e8 h1:4Ceb/pU/u7fKGMCE2DNrWIEHkoR1ELRlYJXzbFOR+E0=
|
||||
github.com/Kr328/tun2socket v0.0.0-20211231120722-962f339492e8/go.mod h1:YR9wK13TgI5ww8iKWm91MHiSoHC7Oz0U4beCCmtXqLw=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
|
@ -480,8 +482,6 @@ github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYN
|
|||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
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/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/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
|
@ -751,6 +751,7 @@ golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm
|
|||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -837,6 +838,7 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
|
@ -947,6 +949,9 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -962,8 +967,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0=
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1029,6 +1035,11 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 h1:Nrf94TOjrvW8nm6N3u2xtbnMZaZudNI9b8nIJH8p8qY=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1 h1:OnYw96PF+CsIMrqWo5QP3Q59q5hY1rFErk/yN3cS+JQ=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1/go.mod h1:EApyTk/ZNrkbZjurHL1nleDYnsPpJYBO7LZEBCyDAHk=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
|
@ -1163,8 +1174,8 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
|
|||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
gvisor.dev/gvisor v0.0.0-20220302074230-2966fe9e53f9 h1:nqrG1uWqWMYbSs2iSnUjRhL/sK9O78Fa170Dmv5F9Ng=
|
||||
gvisor.dev/gvisor v0.0.0-20220302074230-2966fe9e53f9/go.mod h1:V4WNP2Uwtx69eOhvLDSQ734EaTJTaBI3P8KgRAlROsg=
|
||||
gvisor.dev/gvisor v0.0.0-20220308041304-8e3b9b36c99e h1:vG9ldlsp4kcxnVLJ4+KkqZJE3XsZI4KmGX1w78hgPwU=
|
||||
gvisor.dev/gvisor v0.0.0-20220308041304-8e3b9b36c99e/go.mod h1:V4WNP2Uwtx69eOhvLDSQ734EaTJTaBI3P8KgRAlROsg=
|
||||
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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
Loading…
Reference in New Issue
Block a user