chore: use sing-tun to replace old tun_adapter
This commit is contained in:
parent
7e67fb2f58
commit
afd5e48adc
93 changed files with 739 additions and 5101 deletions
2
Makefile
2
Makefile
|
@ -12,7 +12,7 @@ VERSION=$(shell git rev-parse --short HEAD)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
-w -s -buildid='
|
-w -s -buildid='
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
@ -16,16 +15,11 @@ import (
|
||||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||||
"github.com/sagernet/sing-shadowsocks"
|
"github.com/sagernet/sing-shadowsocks"
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
buf.DefaultAllocator = pool.DefaultAllocator
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShadowSocks struct {
|
type ShadowSocks struct {
|
||||||
*Base
|
*Base
|
||||||
method shadowsocks.Method
|
method shadowsocks.Method
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DefaultAllocator = NewAllocator()
|
var defaultAllocator = NewAllocator()
|
||||||
|
|
||||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||||
type Allocator struct {
|
type Allocator struct {
|
||||||
|
|
|
@ -13,9 +13,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Get(size int) []byte {
|
func Get(size int) []byte {
|
||||||
return DefaultAllocator.Get(size)
|
return defaultAllocator.Get(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Put(buf []byte) error {
|
func Put(buf []byte) error {
|
||||||
return DefaultAllocator.Put(buf)
|
return defaultAllocator.Put(buf)
|
||||||
}
|
}
|
||||||
|
|
7
common/pool/sing.go
Normal file
7
common/pool/sing.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package pool
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing/common/buf"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
buf.DefaultAllocator = defaultAllocator
|
||||||
|
}
|
|
@ -15,6 +15,30 @@ import (
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetAutoDetectInterface() (string, error) {
|
||||||
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
if route.Dst == nil {
|
||||||
|
lk, err := netlink.LinkByIndex(route.LinkIndex)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lk.Type() == "tuntap" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return lk.Attrs().Name, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("interface not found")
|
||||||
|
}
|
||||||
|
|
||||||
// NewTcEBpfProgram new redirect to tun ebpf program
|
// NewTcEBpfProgram new redirect to tun ebpf program
|
||||||
func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) {
|
func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) {
|
||||||
tunIface, err := netlink.LinkByName(tunName)
|
tunIface, err := netlink.LinkByName(tunName)
|
||||||
|
|
|
@ -15,3 +15,7 @@ func NewTcEBpfProgram(_ []string, _ string) (*TcEBpfProgram, error) {
|
||||||
func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) {
|
func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) {
|
||||||
return nil, fmt.Errorf("system not supported")
|
return nil, fmt.Errorf("system not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAutoDetectInterface() (string, error) {
|
||||||
|
return "", fmt.Errorf("system not supported")
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,9 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/constant/sniffer"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -31,6 +30,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
"github.com/Dreamacro/clash/constant/sniffer"
|
||||||
snifferTypes "github.com/Dreamacro/clash/constant/sniffer"
|
snifferTypes "github.com/Dreamacro/clash/constant/sniffer"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
@ -114,12 +114,75 @@ type Profile struct {
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Device string `yaml:"device" json:"device"`
|
Device string `yaml:"device" json:"device"`
|
||||||
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
Stack string `yaml:"stack" json:"stack"`
|
||||||
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
|
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
|
||||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||||
TunAddressPrefix netip.Prefix `yaml:"-" json:"-"`
|
|
||||||
RedirectToTun []string `yaml:"-" json:"-"`
|
RedirectToTun []string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
|
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||||
|
Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||||
|
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||||
|
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||||
|
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||||
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||||
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||||
|
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||||
|
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||||
|
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||||
|
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||||
|
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||||
|
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListenPrefix netip.Prefix
|
||||||
|
|
||||||
|
func (p ListenPrefix) MarshalJSON() ([]byte, error) {
|
||||||
|
prefix := netip.Prefix(p)
|
||||||
|
if !prefix.IsValid() {
|
||||||
|
return json.Marshal(nil)
|
||||||
|
}
|
||||||
|
return json.Marshal(prefix.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ListenPrefix) MarshalYAML() (interface{}, error) {
|
||||||
|
prefix := netip.Prefix(p)
|
||||||
|
if !prefix.IsValid() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return prefix.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var value string
|
||||||
|
err := json.Unmarshal(bytes, &value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prefix, err := netip.ParsePrefix(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = ListenPrefix(prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
var value string
|
||||||
|
err := node.Decode(&value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prefix, err := netip.ParsePrefix(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = ListenPrefix(prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ListenPrefix) Build() netip.Prefix {
|
||||||
|
return netip.Prefix(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPTables config
|
// IPTables config
|
||||||
|
@ -189,7 +252,7 @@ type RawFallbackFilter struct {
|
||||||
type RawTun struct {
|
type RawTun struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Device string `yaml:"device" json:"device"`
|
Device string `yaml:"device" json:"device"`
|
||||||
Stack C.TUNStack `yaml:"stack" json:"stack"`
|
Stack string `yaml:"stack" json:"stack"`
|
||||||
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
|
||||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||||
AutoDetectInterface bool `yaml:"auto-detect-interface"`
|
AutoDetectInterface bool `yaml:"auto-detect-interface"`
|
||||||
|
@ -294,7 +357,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
Tun: RawTun{
|
Tun: RawTun{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Device: "",
|
Device: "",
|
||||||
Stack: C.TunGvisor,
|
Stack: "gvisor",
|
||||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||||
AutoRoute: false,
|
AutoRoute: false,
|
||||||
AutoDetectInterface: false,
|
AutoDetectInterface: false,
|
||||||
|
@ -1039,17 +1102,6 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
||||||
if rawTun.Enable && rawTun.AutoDetectInterface {
|
|
||||||
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("Can not find auto detect interface.[%s]", err)
|
|
||||||
} else {
|
|
||||||
log.Warnln("Auto detect interface: %s", autoDetectInterfaceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
general.Interface = autoDetectInterfaceName
|
|
||||||
}
|
|
||||||
|
|
||||||
var dnsHijack []netip.AddrPort
|
var dnsHijack []netip.AddrPort
|
||||||
|
|
||||||
for _, d := range rawTun.DNSHijack {
|
for _, d := range rawTun.DNSHijack {
|
||||||
|
@ -1079,8 +1131,9 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
||||||
DNSHijack: dnsHijack,
|
DNSHijack: dnsHijack,
|
||||||
AutoRoute: rawTun.AutoRoute,
|
AutoRoute: rawTun.AutoRoute,
|
||||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||||
TunAddressPrefix: tunAddressPrefix,
|
|
||||||
RedirectToTun: rawTun.RedirectToTun,
|
RedirectToTun: rawTun.RedirectToTun,
|
||||||
|
Inet4Address: []ListenPrefix{ListenPrefix(tunAddressPrefix)},
|
||||||
|
Inet6Address: []ListenPrefix{ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
go.mod
13
go.mod
|
@ -19,8 +19,9 @@ require (
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
github.com/oschwald/geoip2-golang v1.8.0
|
github.com/oschwald/geoip2-golang v1.8.0
|
||||||
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f
|
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||||
|
github.com/sagernet/sing-tun v0.0.0-20221005115555-9a556307f6a3
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f
|
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
|
@ -34,12 +35,8 @@ require (
|
||||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b
|
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b
|
||||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
|
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c
|
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,13 +61,17 @@ require (
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
|
||||||
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 // indirect
|
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||||
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||||
lukechampine.com/blake3 v1.1.7 // indirect
|
lukechampine.com/blake3 v1.1.7 // indirect
|
||||||
)
|
)
|
||||||
|
|
20
go.sum
20
go.sum
|
@ -178,10 +178,19 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f h1:GX416thAwyc0vHBOal/qplvdhFgYO2dHD5GqADCJ0Ig=
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
|
||||||
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
|
||||||
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
|
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186 h1:ZDlgH6dTozS3ODaYq1GxCj+H8NvYESaex90iX72gadw=
|
||||||
|
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
||||||
|
github.com/sagernet/sing-tun v0.0.0-20221005115555-9a556307f6a3 h1:9Igu/lgB1na+YTSEX6/YtPugAlMRyxLCDb7X+I0gdAE=
|
||||||
|
github.com/sagernet/sing-tun v0.0.0-20221005115555-9a556307f6a3/go.mod h1:qbqV9lwcXJnj1Tw4we7oA6Z8zGE/kCXQBCzuhzRWVw8=
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f h1:xyJ3Wbibcug4DxLi/FCHX2Td667SfieyZv645b8+eEE=
|
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f h1:xyJ3Wbibcug4DxLi/FCHX2Td667SfieyZv645b8+eEE=
|
||||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
|
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
@ -344,6 +353,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
@ -378,12 +388,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/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-20220920152132-bb719d3a6e2c h1:Okh6a1xpnJslG9Mn84pId1Mn+Q8cvpo4HCeeFWHo0cA=
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg=
|
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
|
|
|
@ -2,10 +2,7 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/component/ebpf"
|
"github.com/Dreamacro/clash/listener/sing_tun"
|
||||||
"github.com/Dreamacro/clash/listener/autoredir"
|
|
||||||
"github.com/Dreamacro/clash/listener/inner"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -13,15 +10,16 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
"github.com/Dreamacro/clash/component/ebpf"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/listener/autoredir"
|
||||||
"github.com/Dreamacro/clash/listener/http"
|
"github.com/Dreamacro/clash/listener/http"
|
||||||
|
"github.com/Dreamacro/clash/listener/inner"
|
||||||
"github.com/Dreamacro/clash/listener/mixed"
|
"github.com/Dreamacro/clash/listener/mixed"
|
||||||
"github.com/Dreamacro/clash/listener/redir"
|
"github.com/Dreamacro/clash/listener/redir"
|
||||||
"github.com/Dreamacro/clash/listener/socks"
|
"github.com/Dreamacro/clash/listener/socks"
|
||||||
"github.com/Dreamacro/clash/listener/tproxy"
|
"github.com/Dreamacro/clash/listener/tproxy"
|
||||||
"github.com/Dreamacro/clash/listener/tun"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,7 +38,7 @@ var (
|
||||||
tproxyUDPListener *tproxy.UDPListener
|
tproxyUDPListener *tproxy.UDPListener
|
||||||
mixedListener *mixed.Listener
|
mixedListener *mixed.Listener
|
||||||
mixedUDPLister *socks.UDPListener
|
mixedUDPLister *socks.UDPListener
|
||||||
tunStackListener ipstack.Stack
|
tunLister *sing_tun.Listener
|
||||||
autoRedirListener *autoredir.Listener
|
autoRedirListener *autoredir.Listener
|
||||||
autoRedirProgram *ebpf.TcEBpfProgram
|
autoRedirProgram *ebpf.TcEBpfProgram
|
||||||
tcProgram *ebpf.TcEBpfProgram
|
tcProgram *ebpf.TcEBpfProgram
|
||||||
|
@ -359,7 +357,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tunStackListener, err = tun.New(tunConf, tcpIn, udpIn)
|
tunLister, err = sing_tun.New(*tunConf, tcpIn, udpIn)
|
||||||
|
|
||||||
lastTunConf = tunConf
|
lastTunConf = tunConf
|
||||||
}
|
}
|
||||||
|
@ -429,7 +427,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<-
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultRouteInterfaceName, err := commons.GetAutoDetectInterface()
|
defaultRouteInterfaceName, err := ebpf.GetAutoDetectInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -538,7 +536,7 @@ func hasTunConfigChange(tunConf *config.Tun) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if tunConf.TunAddressPrefix.String() != lastTunConf.TunAddressPrefix.String() {
|
if slices.Equal(tunConf.Inet4Address, lastTunConf.Inet4Address) && slices.Equal(tunConf.Inet6Address, lastTunConf.Inet6Address) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,16 +544,9 @@ func hasTunConfigChange(tunConf *config.Tun) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cleanup(wait bool) {
|
func Cleanup(wait bool) {
|
||||||
if tunStackListener != nil {
|
if tunLister != nil {
|
||||||
_ = tunStackListener.Close()
|
tunLister.Close()
|
||||||
commons.StopDefaultInterfaceChangeMonitor()
|
tunLister = nil
|
||||||
|
|
||||||
if wait {
|
|
||||||
commons.WaitForTunClose(lastTunConf.Device)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commons.CleanupRule()
|
|
||||||
}
|
|
||||||
tunStackListener = nil
|
|
||||||
lastTunConf = nil
|
lastTunConf = nil
|
||||||
}
|
}
|
||||||
|
|
41
listener/sing/log.go
Normal file
41
listener/sing/log.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
L "github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logger struct{}
|
||||||
|
|
||||||
|
func (l logger) Trace(args ...any) {
|
||||||
|
log.Debugln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Debug(args ...any) {
|
||||||
|
log.Debugln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Info(args ...any) {
|
||||||
|
log.Infoln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Warn(args ...any) {
|
||||||
|
log.Warnln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Error(args ...any) {
|
||||||
|
log.Errorln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Fatal(args ...any) {
|
||||||
|
log.Fatalln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Panic(args ...any) {
|
||||||
|
log.Fatalln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
var Logger L.Logger = logger{}
|
143
listener/sing/sing.go
Normal file
143
listener/sing/sing.go
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package sing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
|
||||||
|
vmess "github.com/sagernet/sing-vmess"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListenerHandler struct {
|
||||||
|
TcpIn chan<- C.ConnContext
|
||||||
|
UdpIn chan<- *inbound.PacketAdapter
|
||||||
|
Type C.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
type waitCloseConn struct {
|
||||||
|
net.Conn
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
close sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *waitCloseConn) Close() error { // call from handleTCPConn(connCtx C.ConnContext)
|
||||||
|
c.close.Do(func() {
|
||||||
|
c.wg.Done()
|
||||||
|
})
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
switch metadata.Destination.Fqdn {
|
||||||
|
case vmess.MuxDestination.Fqdn:
|
||||||
|
return vmess.HandleMuxConnection(ctx, conn, h)
|
||||||
|
case uot.UOTMagicAddress:
|
||||||
|
metadata.Destination = M.Socksaddr{}
|
||||||
|
return h.NewPacketConnection(ctx, uot.NewClientConn(conn), metadata)
|
||||||
|
}
|
||||||
|
target := socks5.ParseAddr(metadata.Destination.String())
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
defer wg.Wait() // this goroutine must exit after conn.Close()
|
||||||
|
wg.Add(1)
|
||||||
|
h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{Conn: conn, wg: wg}, h.Type)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
|
||||||
|
defer func() { _ = conn.Close() }()
|
||||||
|
mutex := sync.Mutex{}
|
||||||
|
conn2 := conn // a new interface to set nil in defer
|
||||||
|
defer func() {
|
||||||
|
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
|
||||||
|
defer mutex.Unlock()
|
||||||
|
conn2 = nil
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
buff := buf.NewPacket() // do not use stack buffer
|
||||||
|
dest, err := conn.ReadPacket(buff)
|
||||||
|
if err != nil {
|
||||||
|
buff.Release()
|
||||||
|
if E.IsClosed(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target := socks5.ParseAddr(dest.String())
|
||||||
|
packet := &packet{
|
||||||
|
conn: &conn2,
|
||||||
|
mutex: &mutex,
|
||||||
|
rAddr: metadata.Source.UDPAddr(),
|
||||||
|
lAddr: conn.LocalAddr(),
|
||||||
|
buff: buff,
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case h.UdpIn <- inbound.NewPacket(target, packet, h.Type):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) NewError(ctx context.Context, err error) {
|
||||||
|
log.Warnln("%s listener get error: %+v", h.Type.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
conn *network.PacketConn
|
||||||
|
mutex *sync.Mutex
|
||||||
|
rAddr net.Addr
|
||||||
|
lAddr net.Addr
|
||||||
|
buff *buf.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Data() []byte {
|
||||||
|
return c.buff.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||||
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
if addr == nil {
|
||||||
|
err = errors.New("address is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buff := buf.NewPacket()
|
||||||
|
defer buff.Release()
|
||||||
|
n, err = buff.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
conn := *c.conn
|
||||||
|
if conn == nil {
|
||||||
|
err = errors.New("writeBack to closed connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WritePacket(buff, M.ParseSocksaddr(addr.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the source IP/Port of UDP Packet
|
||||||
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
|
return c.rAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Drop() {
|
||||||
|
c.buff.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) InAddr() net.Addr {
|
||||||
|
return c.lAddr
|
||||||
|
}
|
165
listener/sing_tun/dns.go
Normal file
165
listener/sing_tun/dns.go
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
package sing_tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
"github.com/Dreamacro/clash/listener/sing"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultDnsReadTimeout = time.Second * 10
|
||||||
|
|
||||||
|
type ListenerHandler struct {
|
||||||
|
sing.ListenerHandler
|
||||||
|
DnsAdds []netip.AddrPort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
|
||||||
|
if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, addrPort := range h.DnsAdds {
|
||||||
|
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
|
||||||
|
log.Debugln("[DNS] hijack tcp:%s", metadata.Destination.String())
|
||||||
|
buff := pool.Get(pool.UDPBufferSize)
|
||||||
|
defer func() {
|
||||||
|
_ = pool.Put(buff)
|
||||||
|
_ = conn.Close()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
if conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
length := uint16(0)
|
||||||
|
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(length) > len(buff) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := io.ReadFull(conn, buff[:length])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = func() error {
|
||||||
|
inData := buff[:n]
|
||||||
|
msg, err := RelayDnsPacket(inData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return h.ListenerHandler.NewConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
|
||||||
|
if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
|
||||||
|
log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String())
|
||||||
|
defer func() { _ = conn.Close() }()
|
||||||
|
mutex := sync.Mutex{}
|
||||||
|
conn2 := conn // a new interface to set nil in defer
|
||||||
|
defer func() {
|
||||||
|
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
|
||||||
|
defer mutex.Unlock()
|
||||||
|
conn2 = nil
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
buff := buf.NewPacket()
|
||||||
|
dest, err := conn.ReadPacket(buff)
|
||||||
|
if err != nil {
|
||||||
|
buff.Release()
|
||||||
|
if E.IsClosed(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
inData := buff.Bytes()
|
||||||
|
msg, err := RelayDnsPacket(inData)
|
||||||
|
if err != nil {
|
||||||
|
buff.Release()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
_, err = buff.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
buff.Release()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
conn := conn2
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WritePacket(buff, dest) // WritePacket will release buff
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
||||||
|
msg := &D.Msg{}
|
||||||
|
if err := msg.Unpack(payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := resolver.ServeMsg(msg)
|
||||||
|
if err != nil {
|
||||||
|
m := new(D.Msg)
|
||||||
|
m.SetRcode(msg, D.RcodeServerFailure)
|
||||||
|
return m.Pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
r.SetRcode(msg, r.Rcode)
|
||||||
|
r.Compress = true
|
||||||
|
return r.Pack()
|
||||||
|
}
|
237
listener/sing_tun/server.go
Normal file
237
listener/sing_tun/server.go
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
package sing_tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
"github.com/Dreamacro/clash/config"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/listener/sing"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ranges"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InterfaceName = "Meta"
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
closed bool
|
||||||
|
options config.Tun
|
||||||
|
handler tun.Handler
|
||||||
|
|
||||||
|
tunIf tun.Tun
|
||||||
|
tunStack tun.Stack
|
||||||
|
|
||||||
|
networkUpdateMonitor tun.NetworkUpdateMonitor
|
||||||
|
defaultInterfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (l *Listener, err error) {
|
||||||
|
tunName := options.Device
|
||||||
|
if tunName == "" {
|
||||||
|
tunName = tun.CalculateInterfaceName(InterfaceName)
|
||||||
|
}
|
||||||
|
tunMTU := options.MTU
|
||||||
|
if tunMTU == 0 {
|
||||||
|
tunMTU = 9000
|
||||||
|
}
|
||||||
|
var udpTimeout int64
|
||||||
|
if options.UDPTimeout != 0 {
|
||||||
|
udpTimeout = options.UDPTimeout
|
||||||
|
} else {
|
||||||
|
udpTimeout = int64(C.DefaultUDPTimeout.Seconds())
|
||||||
|
}
|
||||||
|
includeUID := uidToRange(options.IncludeUID)
|
||||||
|
if len(options.IncludeUIDRange) > 0 {
|
||||||
|
var err error
|
||||||
|
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse include_uid_range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
excludeUID := uidToRange(options.ExcludeUID)
|
||||||
|
if len(options.ExcludeUIDRange) > 0 {
|
||||||
|
var err error
|
||||||
|
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsAdds []netip.AddrPort
|
||||||
|
|
||||||
|
for _, d := range options.DNSHijack {
|
||||||
|
dnsAdds = append(dnsAdds, d)
|
||||||
|
}
|
||||||
|
for _, a := range options.Inet4Address {
|
||||||
|
addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53)
|
||||||
|
dnsAdds = append(dnsAdds, addrPort)
|
||||||
|
}
|
||||||
|
for _, a := range options.Inet6Address {
|
||||||
|
addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53)
|
||||||
|
dnsAdds = append(dnsAdds, addrPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := &ListenerHandler{
|
||||||
|
ListenerHandler: sing.ListenerHandler{
|
||||||
|
TcpIn: tcpIn,
|
||||||
|
UdpIn: udpIn,
|
||||||
|
Type: C.TUN,
|
||||||
|
},
|
||||||
|
DnsAdds: dnsAdds,
|
||||||
|
}
|
||||||
|
l = &Listener{
|
||||||
|
closed: false,
|
||||||
|
options: options,
|
||||||
|
handler: handler,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
l.Close()
|
||||||
|
l = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(handler)
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "create NetworkUpdateMonitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = networkUpdateMonitor.Start()
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "start NetworkUpdateMonitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.networkUpdateMonitor = networkUpdateMonitor
|
||||||
|
|
||||||
|
defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, tun.DefaultInterfaceMonitorOptions{})
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "create DefaultInterfaceMonitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaultInterfaceMonitor.RegisterCallback(func(event int) error {
|
||||||
|
targetInterface := dialer.DefaultInterface.Load()
|
||||||
|
autoDetectInterfaceName := defaultInterfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||||
|
if autoDetectInterfaceName != "" && autoDetectInterfaceName != "<nil>" {
|
||||||
|
targetInterface = autoDetectInterfaceName
|
||||||
|
} else {
|
||||||
|
log.Warnln("Auto detect interface name is empty.")
|
||||||
|
}
|
||||||
|
if old := dialer.DefaultInterface.Load(); old != targetInterface {
|
||||||
|
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, targetInterface)
|
||||||
|
|
||||||
|
dialer.DefaultInterface.Store(targetInterface)
|
||||||
|
|
||||||
|
iface.FlushCache()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
err = defaultInterfaceMonitor.Start()
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "start DefaultInterfaceMonitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.defaultInterfaceMonitor = defaultInterfaceMonitor
|
||||||
|
|
||||||
|
tunOptions := tun.Options{
|
||||||
|
Name: tunName,
|
||||||
|
MTU: tunMTU,
|
||||||
|
Inet4Address: common.Map(options.Inet4Address, config.ListenPrefix.Build),
|
||||||
|
Inet6Address: common.Map(options.Inet6Address, config.ListenPrefix.Build),
|
||||||
|
AutoRoute: options.AutoRoute,
|
||||||
|
StrictRoute: options.StrictRoute,
|
||||||
|
IncludeUID: includeUID,
|
||||||
|
ExcludeUID: excludeUID,
|
||||||
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
|
IncludePackage: options.IncludePackage,
|
||||||
|
ExcludePackage: options.ExcludePackage,
|
||||||
|
InterfaceMonitor: defaultInterfaceMonitor,
|
||||||
|
TableIndex: 2022,
|
||||||
|
}
|
||||||
|
|
||||||
|
//if C.IsAndroid {
|
||||||
|
// t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
|
//}
|
||||||
|
tunIf, err := tun.Open(tunOptions)
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "configure tun interface")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.tunIf = tunIf
|
||||||
|
l.tunStack, err = tun.NewStack(options.Stack, tun.StackOptions{
|
||||||
|
Context: context.TODO(),
|
||||||
|
Tun: tunIf,
|
||||||
|
MTU: tunOptions.MTU,
|
||||||
|
Name: tunOptions.Name,
|
||||||
|
Inet4Address: tunOptions.Inet4Address,
|
||||||
|
Inet6Address: tunOptions.Inet6Address,
|
||||||
|
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||||
|
UDPTimeout: udpTimeout,
|
||||||
|
Handler: handler,
|
||||||
|
Logger: sing.Logger,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = l.tunStack.Start()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infoln("Tun adapter listening at: %s(%s,%s), mtu: %d, auto route: %v, ip stack: %s",
|
||||||
|
tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.Stack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func uidToRange(uidList []uint32) []ranges.Range[uint32] {
|
||||||
|
return common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
|
||||||
|
return ranges.NewSingle(uid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) {
|
||||||
|
for _, uidRange := range rangeList {
|
||||||
|
if !strings.Contains(uidRange, ":") {
|
||||||
|
return nil, E.New("missing ':' in range: ", uidRange)
|
||||||
|
}
|
||||||
|
subIndex := strings.Index(uidRange, ":")
|
||||||
|
if subIndex == 0 {
|
||||||
|
return nil, E.New("missing range start: ", uidRange)
|
||||||
|
} else if subIndex == len(uidRange)-1 {
|
||||||
|
return nil, E.New("missing range end: ", uidRange)
|
||||||
|
}
|
||||||
|
var start, end uint64
|
||||||
|
var err error
|
||||||
|
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse range start")
|
||||||
|
}
|
||||||
|
end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse range end")
|
||||||
|
}
|
||||||
|
uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end)))
|
||||||
|
}
|
||||||
|
return uidRanges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() {
|
||||||
|
l.closed = true
|
||||||
|
_ = common.Close(
|
||||||
|
l.tunStack,
|
||||||
|
l.tunIf,
|
||||||
|
l.defaultInterfaceMonitor,
|
||||||
|
l.networkUpdateMonitor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Config() config.Tun {
|
||||||
|
return l.options
|
||||||
|
}
|
7
listener/sing_tun/server_windows.go
Normal file
7
listener/sing_tun/server_windows.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package sing_tun
|
||||||
|
|
||||||
|
import tun "github.com/sagernet/sing-tun"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tun.TunnelType = InterfaceName
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
//go:build no_gvisor
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
// Device is the interface that implemented by network layer devices (e.g. tun),
|
|
||||||
// and easy to use as stack.LinkEndpoint.
|
|
||||||
type Device interface {
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package fdbased
|
|
||||||
|
|
||||||
const Driver = "fd"
|
|
||||||
|
|
||||||
const defaultMTU = 1500
|
|
|
@ -1,49 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
|
@ -1,17 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FD struct {
|
|
||||||
stack.LinkEndpoint
|
|
||||||
|
|
||||||
fd int
|
|
||||||
mtu uint32
|
|
||||||
|
|
||||||
file *os.File
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
//go:build no_gvisor
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FD struct {
|
|
||||||
fd int
|
|
||||||
mtu uint32
|
|
||||||
|
|
||||||
file *os.File
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
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")
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open(fd int, mtu uint32) (device.Device, error) {
|
|
||||||
f := &FD{fd: fd, mtu: mtu}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) useEndpoint() error {
|
|
||||||
return f.newLinuxEp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) useIOBased() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) Read(packet []byte) (int, error) {
|
|
||||||
return f.read(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) Write(packet []byte) (int, error) {
|
|
||||||
return f.write(packet)
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
//go:build !no_gvisor && (linux || android)
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *FD) newLinuxEp() 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) 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
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
//go:build no_gvisor
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *FD) newLinuxEp() error {
|
|
||||||
return fmt.Errorf("unsupported gvisor on the build")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) read(packet []byte) (int, error) {
|
|
||||||
return 0, fmt.Errorf("unsupported gvisor on the build")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) write(packet []byte) (int, error) {
|
|
||||||
return 0, fmt.Errorf("unsupported gvisor on the build")
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
//go:build !linux && !windows
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open(fd int, mtu uint32) (device.Device, error) {
|
|
||||||
f := &FD{fd: fd, mtu: mtu}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FD) useEndpoint() error {
|
|
||||||
return f.newEpOther()
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
//go:build !no_gvisor && !linux && !windows
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *FD) newEpOther() 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
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
//go:build no_gvisor && !linux && !windows
|
|
||||||
|
|
||||||
package fdbased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *FD) newEpOther() error {
|
|
||||||
return fmt.Errorf("unsupported gvisor on the build")
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
// Package iobased provides the implementation of io.ReadWriter
|
|
||||||
// based data-link layer endpoints.
|
|
||||||
package iobased
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"gvisor.dev/gvisor/pkg/bufferv2"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"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
|
|
||||||
|
|
||||||
// wg keeps track of running goroutines.
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) Wait() {
|
|
||||||
e.wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.Endpoint.Attach(dispatcher)
|
|
||||||
e.once.Do(func() {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
e.wg.Add(2)
|
|
||||||
go func() {
|
|
||||||
e.outboundLoop(ctx)
|
|
||||||
e.wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
e.dispatchLoop(cancel)
|
|
||||||
e.wg.Done()
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
mtu := int(e.mtu)
|
|
||||||
for {
|
|
||||||
data := pool.Get(mtu)
|
|
||||||
|
|
||||||
n, err := e.rw.Read(data)
|
|
||||||
if err != nil {
|
|
||||||
_ = pool.Put(data)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if n == 0 || n > mtu {
|
|
||||||
_ = pool.Put(data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !e.IsAttached() {
|
|
||||||
_ = pool.Put(data)
|
|
||||||
continue /* unattached, drop packet */
|
|
||||||
}
|
|
||||||
|
|
||||||
var p tcpip.NetworkProtocolNumber
|
|
||||||
switch header.IPVersion(data) {
|
|
||||||
case header.IPv4Version:
|
|
||||||
p = header.IPv4ProtocolNumber
|
|
||||||
case header.IPv6Version:
|
|
||||||
p = header.IPv6ProtocolNumber
|
|
||||||
default:
|
|
||||||
_ = pool.Put(data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
||||||
Payload: bufferv2.MakeWithData(data),
|
|
||||||
OnRelease: func() {
|
|
||||||
_ = pool.Put(data)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
e.InjectInbound(p, pkt)
|
|
||||||
|
|
||||||
pkt.DecRef()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
pktView := pkt.ToView()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
pktView.Release()
|
|
||||||
pkt.DecRef()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := e.rw.Write(pktView.AsSlice()); err != nil {
|
|
||||||
return &tcpip.ErrInvalidEndpointState{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package iobased
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,233 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:linkname modwintun golang.zx2c4.com/wintun.modwintun
|
|
||||||
|
|
||||||
//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter
|
|
||||||
|
|
||||||
//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter
|
|
||||||
|
|
||||||
//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter
|
|
||||||
|
|
||||||
//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver
|
|
||||||
|
|
||||||
//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID
|
|
||||||
|
|
||||||
//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion
|
|
||||||
|
|
||||||
//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket
|
|
||||||
|
|
||||||
//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession
|
|
||||||
|
|
||||||
//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent
|
|
||||||
|
|
||||||
//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket
|
|
||||||
|
|
||||||
//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket
|
|
||||||
|
|
||||||
//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket
|
|
||||||
|
|
||||||
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
|
|
||||||
|
|
||||||
var (
|
|
||||||
modwintun *lazyDLL
|
|
||||||
procWintunCreateAdapter *lazyProc
|
|
||||||
procWintunOpenAdapter *lazyProc
|
|
||||||
procWintunCloseAdapter *lazyProc
|
|
||||||
procWintunDeleteDriver *lazyProc
|
|
||||||
procWintunGetAdapterLUID *lazyProc
|
|
||||||
procWintunGetRunningDriverVersion *lazyProc
|
|
||||||
procWintunAllocateSendPacket *lazyProc
|
|
||||||
procWintunEndSession *lazyProc
|
|
||||||
procWintunGetReadWaitEvent *lazyProc
|
|
||||||
procWintunReceivePacket *lazyProc
|
|
||||||
procWintunReleaseReceivePacket *lazyProc
|
|
||||||
procWintunSendPacket *lazyProc
|
|
||||||
procWintunStartSession *lazyProc
|
|
||||||
)
|
|
||||||
|
|
||||||
type loggerLevel int
|
|
||||||
|
|
||||||
const (
|
|
||||||
logInfo loggerLevel = iota
|
|
||||||
logWarn
|
|
||||||
logErr
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
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")
|
|
||||||
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 InitWintun() (err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = fmt.Errorf("init wintun.dll error: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = modwintun.Load(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
procWintunCreateAdapter.Addr()
|
|
||||||
procWintunOpenAdapter.Addr()
|
|
||||||
procWintunCloseAdapter.Addr()
|
|
||||||
procWintunDeleteDriver.Addr()
|
|
||||||
procWintunGetAdapterLUID.Addr()
|
|
||||||
procWintunGetRunningDriverVersion.Addr()
|
|
||||||
procWintunAllocateSendPacket.Addr()
|
|
||||||
procWintunEndSession.Addr()
|
|
||||||
procWintunGetReadWaitEvent.Addr()
|
|
||||||
procWintunReceivePacket.Addr()
|
|
||||||
procWintunReleaseReceivePacket.Addr()
|
|
||||||
procWintunSendPacket.Addr()
|
|
||||||
procWintunStartSession.Addr()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
|
||||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
|
|
||||||
switch level {
|
|
||||||
case logInfo:
|
|
||||||
log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg))
|
|
||||||
case logWarn:
|
|
||||||
log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg))
|
|
||||||
case logErr:
|
|
||||||
log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg))
|
|
||||||
default:
|
|
||||||
log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg))
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupLogger(dll *lazyDLL) {
|
|
||||||
var callback uintptr
|
|
||||||
if runtime.GOARCH == "386" {
|
|
||||||
callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int {
|
|
||||||
return logMessage(level, 0, msg)
|
|
||||||
})
|
|
||||||
} else if runtime.GOARCH == "arm" {
|
|
||||||
callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int {
|
|
||||||
return logMessage(level, 0, msg)
|
|
||||||
})
|
|
||||||
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
|
||||||
callback = windows.NewCallback(logMessage)
|
|
||||||
}
|
|
||||||
_, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err)
|
|
||||||
}
|
|
||||||
addr, err := p.nameToAddr()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error getting %s 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lazyProc) Load() error {
|
|
||||||
return p.dll.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
type lazyDLL struct {
|
|
||||||
Name string
|
|
||||||
Base windows.Handle
|
|
||||||
mu sync.Mutex
|
|
||||||
module *memmod.Module
|
|
||||||
onLoad func(d *lazyDLL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *lazyDLL) Load() error {
|
|
||||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
if d.module != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
module, err := memmod.LoadLibrary(dllContent)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load library: %w", err)
|
|
||||||
}
|
|
||||||
d.Base = windows.Handle(module.BaseAddr())
|
|
||||||
|
|
||||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
|
||||||
if d.onLoad != nil {
|
|
||||||
d.onLoad(d)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
|
||||||
return p.dll.module.ProcAddressByName(p.Name)
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed x86/wintun.dll
|
|
||||||
var dllContent []byte
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed amd64/wintun.dll
|
|
||||||
var dllContent []byte
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed arm/wintun.dll
|
|
||||||
var dllContent []byte
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed arm64/wintun.dll
|
|
||||||
var dllContent []byte
|
|
|
@ -1,10 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package driver
|
|
Binary file not shown.
|
@ -1,14 +0,0 @@
|
||||||
// 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)
|
|
|
@ -1,145 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Open(name string, mtu uint32) (_ device.Device, err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = fmt.Errorf("open tun: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
t := &TUN{
|
|
||||||
name: name,
|
|
||||||
mtu: mtu,
|
|
||||||
offset: offset,
|
|
||||||
}
|
|
||||||
|
|
||||||
forcedMTU := defaultMTU
|
|
||||||
if t.mtu > 0 {
|
|
||||||
forcedMTU = int(t.mtu)
|
|
||||||
}
|
|
||||||
|
|
||||||
nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
|
||||||
|
|
||||||
// retry if abnormal exit at last time on Windows
|
|
||||||
if err != nil && runtime.GOOS == "windows" && os.IsExist(err) {
|
|
||||||
nt, err = newDevice(t.name, forcedMTU)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if t.offset > 0 {
|
|
||||||
t.cache = make([]byte, 65535)
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TUN) Read(packet []byte) (int, error) {
|
|
||||||
if t.offset == 0 {
|
|
||||||
return t.nt.Read(packet, t.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := t.nt.Read(t.cache, t.offset)
|
|
||||||
|
|
||||||
copy(packet, t.cache[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(t.cache[:t.offset], packet...)
|
|
||||||
|
|
||||||
return t.nt.Write(packet, t.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TUN) Close() error {
|
|
||||||
defer closeIO(t)
|
|
||||||
return t.nt.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TUN) Name() string {
|
|
||||||
name, _ := t.nt.Name()
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TUN) UseEndpoint() error {
|
|
||||||
return newEq(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TUN) UseIOBased() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//go:build !linux && !no_gvisor
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"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
|
|
||||||
|
|
||||||
cache []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeIO(t *TUN) {
|
|
||||||
if t.Endpoint != nil {
|
|
||||||
t.Endpoint.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEq(t *TUN) 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
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//go:build !linux && no_gvisor
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TUN struct {
|
|
||||||
nt *tun.NativeTun
|
|
||||||
mtu uint32
|
|
||||||
name string
|
|
||||||
offset int
|
|
||||||
|
|
||||||
cache []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeIO(t *TUN) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEq(t *TUN) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
//go:build !linux && !windows
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import "golang.zx2c4.com/wireguard/tun"
|
|
||||||
|
|
||||||
const (
|
|
||||||
offset = 4 /* 4 bytes TUN_PI */
|
|
||||||
defaultMTU = 1500
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDevice(name string, mtu int) (tun.Device, error) {
|
|
||||||
return tun.CreateTUN(name, mtu)
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device/tun/driver"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
offset = 0
|
|
||||||
defaultMTU = 0 /* auto */
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
guid, _ := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}")
|
|
||||||
|
|
||||||
tun.WintunTunnelType = "Meta"
|
|
||||||
tun.WintunStaticRequestedGUID = &guid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TUN) LUID() uint64 {
|
|
||||||
return t.nt.LUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDevice(name string, mtu int) (nt tun.Device, err error) {
|
|
||||||
if err = driver.InitWintun(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return tun.CreateTUN(name, mtu)
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
monitorStarted = atomic.NewBool(false)
|
|
||||||
monitorStop = make(chan struct{}, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartDefaultInterfaceChangeMonitor() {
|
|
||||||
go func() {
|
|
||||||
if monitorStarted.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
monitorStarted.Store(true)
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
ch := make(chan netlink.RouteUpdate, 2)
|
|
||||||
err := netlink.RouteSubscribe(ch, done)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[TUN] auto detect interface fail: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debugln("[TUN] start auto detect interface monitor")
|
|
||||||
defer func() {
|
|
||||||
close(done)
|
|
||||||
monitorStarted.Store(false)
|
|
||||||
log.Debugln("[TUN] stop auto detect interface monitor")
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-monitorStop:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-monitorStop:
|
|
||||||
return
|
|
||||||
case <-ch:
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaceName, err := GetAutoDetectInterface()
|
|
||||||
if err != nil {
|
|
||||||
t := time.NewTicker(2 * time.Second)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ch <- <-ch:
|
|
||||||
break
|
|
||||||
case <-t.C:
|
|
||||||
interfaceName, err = GetAutoDetectInterface()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
t.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[TUN] detect interface: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
old := dialer.DefaultInterface.Load()
|
|
||||||
if interfaceName == old {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer.DefaultInterface.Store(interfaceName)
|
|
||||||
iface.FlushCache()
|
|
||||||
|
|
||||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func StopDefaultInterfaceChangeMonitor() {
|
|
||||||
if monitorStarted.Load() {
|
|
||||||
monitorStop <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
monitorDuration = 3 * time.Second
|
|
||||||
monitorStarted = atomic.NewBool(false)
|
|
||||||
monitorStop = make(chan struct{}, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartDefaultInterfaceChangeMonitor() {
|
|
||||||
go func() {
|
|
||||||
if monitorStarted.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
monitorStarted.Store(true)
|
|
||||||
t := time.NewTicker(monitorDuration)
|
|
||||||
log.Debugln("[TUN] start auto detect interface monitor")
|
|
||||||
defer func() {
|
|
||||||
monitorStarted.Store(false)
|
|
||||||
t.Stop()
|
|
||||||
log.Debugln("[TUN] stop auto detect interface monitor")
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-monitorStop:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
interfaceName, err := GetAutoDetectInterface()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
old := dialer.DefaultInterface.Load()
|
|
||||||
if interfaceName == old {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer.DefaultInterface.Store(interfaceName)
|
|
||||||
iface.FlushCache()
|
|
||||||
|
|
||||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
|
||||||
case <-monitorStop:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func StopDefaultInterfaceChangeMonitor() {
|
|
||||||
if monitorStarted.Load() {
|
|
||||||
monitorStop <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
D "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultDnsReadTimeout = time.Second * 10
|
|
||||||
|
|
||||||
func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool {
|
|
||||||
for _, addrPort := range dnsAdds {
|
|
||||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
|
||||||
msg := &D.Msg{}
|
|
||||||
if err := msg.Unpack(payload); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := resolver.ServeMsg(msg)
|
|
||||||
if err != nil {
|
|
||||||
m := new(D.Msg)
|
|
||||||
m.SetRcode(msg, D.RcodeServerFailure)
|
|
||||||
return m.Pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SetRcode(msg, r.Rcode)
|
|
||||||
r.Compress = true
|
|
||||||
return r.Pack()
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func ipv4MaskString(bits int) string {
|
|
||||||
m := net.CIDRMask(bits, 32)
|
|
||||||
if len(m) != 4 {
|
|
||||||
panic("ipv4Mask: len must be 4 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
func WaitForTunClose(deviceName string) {
|
|
||||||
t := time.NewTicker(600 * time.Millisecond)
|
|
||||||
defer t.Stop()
|
|
||||||
log.Debugln("[TUN] waiting for device close")
|
|
||||||
for {
|
|
||||||
<-t.C
|
|
||||||
interfaces, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for i := len(interfaces) - 1; i > -1; i-- {
|
|
||||||
if interfaces[i].Name == deviceName {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAutoDetectInterface() (ifn string, err error) {
|
|
||||||
routes, err := netlink.RouteGetWithOptions(
|
|
||||||
net.ParseIP("1.1.1.1"),
|
|
||||||
&netlink.RouteGetOptions{
|
|
||||||
Uid: &netlink.UID{Uid: 4294967295},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range routes {
|
|
||||||
if lk, err := netlink.LinkByIndex(route.LinkIndex); err == nil {
|
|
||||||
return lk.Attrs().Name, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("interface not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
|
||||||
var (
|
|
||||||
interfaceName = dev.Name()
|
|
||||||
ip = addr.Masked().Addr().Next()
|
|
||||||
)
|
|
||||||
|
|
||||||
metaLink, err := netlink.LinkByName(interfaceName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
naddr, err := netlink.ParseAddr(addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = netlink.AddrAdd(metaLink, naddr); err != nil && err.Error() != "file exists" {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = netlink.LinkSetUp(metaLink); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if autoRoute {
|
|
||||||
err = configInterfaceRouting(metaLink.Attrs().Index, interfaceName, ip)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func configInterfaceRouting(index int, interfaceName string, ip netip.Addr) error {
|
|
||||||
const tableId = 1981801
|
|
||||||
var pref = 9000
|
|
||||||
|
|
||||||
for _, route := range defaultRoutes {
|
|
||||||
_, ipn, err := net.ParseCIDR(route)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.RouteAdd(&netlink.Route{
|
|
||||||
LinkIndex: index,
|
|
||||||
Scope: netlink.SCOPE_LINK,
|
|
||||||
Protocol: 2,
|
|
||||||
Src: ip.AsSlice(),
|
|
||||||
Dst: ipn,
|
|
||||||
Table: tableId,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logIfErr := func(e error) {
|
|
||||||
if e != nil {
|
|
||||||
log.Warnln("[TOUTE] config route rule: %s", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r *netlink.Rule
|
|
||||||
r = netlink.NewRule()
|
|
||||||
r.Table = 254
|
|
||||||
r.Priority = pref
|
|
||||||
logIfErr(netlink.RuleAdd(r))
|
|
||||||
pref += 10
|
|
||||||
|
|
||||||
r = netlink.NewRule()
|
|
||||||
_, nl, _ := net.ParseCIDR("0.0.0.0/32")
|
|
||||||
r.Table = tableId
|
|
||||||
r.Priority = pref
|
|
||||||
r.Src = nl
|
|
||||||
r.IifName = "lo"
|
|
||||||
r.UID = netlink.NewRuleUIDRange(0, 4294967294)
|
|
||||||
logIfErr(netlink.RuleAdd(r))
|
|
||||||
pref += 10
|
|
||||||
|
|
||||||
_, nl, _ = net.ParseCIDR(ip.String())
|
|
||||||
r.Priority = pref
|
|
||||||
r.Src = nl
|
|
||||||
logIfErr(netlink.RuleAdd(r))
|
|
||||||
pref += 10
|
|
||||||
|
|
||||||
r = netlink.NewRule()
|
|
||||||
r.Table = 254
|
|
||||||
r.Priority = pref
|
|
||||||
r.IifName = interfaceName
|
|
||||||
r.SuppressPrefixlen = 0
|
|
||||||
logIfErr(netlink.RuleAdd(r))
|
|
||||||
pref += 10
|
|
||||||
|
|
||||||
r = netlink.NewRule()
|
|
||||||
r.Table = tableId
|
|
||||||
r.Priority = pref
|
|
||||||
r.IifName = "lo"
|
|
||||||
r.SuppressPrefixlen = 0
|
|
||||||
r.Invert = true
|
|
||||||
logIfErr(netlink.RuleAdd(r))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CleanupRule() {
|
|
||||||
r := netlink.NewRule()
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
r.Priority = 9000 + i*10
|
|
||||||
err := netlink.RuleDel(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[TOUTE] cleanup route rule: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cmd"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
return cmd.ExecCmd("bash -c route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
|
||||||
if !addr.Addr().Is4() {
|
|
||||||
return fmt.Errorf("supported ipv4 only")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
interfaceName = dev.Name()
|
|
||||||
ip = addr.Masked().Addr().Next()
|
|
||||||
gw = ip.Next()
|
|
||||||
netmask = ipv4MaskString(addr.Bits())
|
|
||||||
)
|
|
||||||
|
|
||||||
cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw)
|
|
||||||
|
|
||||||
_, err := cmd.ExecCmd(cmdStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = cmd.ExecCmd(fmt.Sprintf("ipconfig set %s automatic-v6", interfaceName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if autoRoute {
|
|
||||||
err = configInterfaceRouting(interfaceName, addr)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
|
||||||
var (
|
|
||||||
routes = append(defaultRoutes, addr.String())
|
|
||||||
gateway = addr.Masked().Addr().Next()
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, destination := range routes {
|
|
||||||
if _, err := cmd.ExecCmd(fmt.Sprintf("route add -net %s %s", destination, gateway)); 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func CleanupRule() {}
|
|
|
@ -1,89 +0,0 @@
|
||||||
//go:build !android
|
|
||||||
|
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range routes {
|
|
||||||
if route.Dst == nil {
|
|
||||||
lk, err := netlink.LinkByIndex(route.LinkIndex)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if lk.Type() == "tuntap" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return lk.Attrs().Name, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("interface not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
|
||||||
var (
|
|
||||||
interfaceName = dev.Name()
|
|
||||||
ip = addr.Masked().Addr().Next()
|
|
||||||
)
|
|
||||||
|
|
||||||
metaLink, err := netlink.LinkByName(interfaceName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nlAddr, err := netlink.ParseAddr(addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = netlink.AddrAdd(metaLink, nlAddr); err != nil && err.Error() != "file exists" {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = netlink.LinkSetUp(metaLink); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if autoRoute {
|
|
||||||
_ = configInterfaceRouting(metaLink.Attrs().Index, ip)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configInterfaceRouting(index int, ip netip.Addr) error {
|
|
||||||
for _, route := range defaultRoutes {
|
|
||||||
_, ipn, err := net.ParseCIDR(route)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.RouteAdd(&netlink.Route{
|
|
||||||
LinkIndex: index,
|
|
||||||
Scope: netlink.SCOPE_LINK,
|
|
||||||
Protocol: 2,
|
|
||||||
Src: ip.AsSlice(),
|
|
||||||
Dst: ipn,
|
|
||||||
Table: 254,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CleanupRule() {}
|
|
|
@ -1,21 +0,0 @@
|
||||||
//go:build !darwin && !linux && !windows
|
|
||||||
|
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
return "", fmt.Errorf("can not auto detect interface-name on this OS: %s, you must be detecting interface-name by manual", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
|
||||||
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CleanupRule() {}
|
|
|
@ -1,273 +0,0 @@
|
||||||
package commons
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.zx2c4.com/wireguard/windows/services"
|
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wintunInterfaceName string
|
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
|
||||||
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
|
||||||
if err == nil {
|
|
||||||
return ifname, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
|
||||||
retryOnFailure := services.StartedAtBoot()
|
|
||||||
tryTimes := 0
|
|
||||||
var err error
|
|
||||||
startOver:
|
|
||||||
if tryTimes > 0 {
|
|
||||||
log.Infoln("[TUN] retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
retryOnFailure = retryOnFailure && tryTimes < 15
|
|
||||||
}
|
|
||||||
tryTimes++
|
|
||||||
|
|
||||||
var (
|
|
||||||
luid = winipcfg.LUID(dev.(*tun.TUN).LUID())
|
|
||||||
ip = addr.Masked().Addr().Next()
|
|
||||||
gw = ip.Next()
|
|
||||||
addresses = []netip.Prefix{netip.PrefixFrom(ip, addr.Bits())}
|
|
||||||
dnsAddress = []netip.Addr{gw}
|
|
||||||
|
|
||||||
family4 = winipcfg.AddressFamily(windows.AF_INET)
|
|
||||||
familyV6 = winipcfg.AddressFamily(windows.AF_INET6)
|
|
||||||
currentFamily = winipcfg.AddressFamily(windows.AF_INET6)
|
|
||||||
)
|
|
||||||
|
|
||||||
if addr.Addr().Is4() {
|
|
||||||
currentFamily = winipcfg.AddressFamily(windows.AF_INET)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = luid.FlushRoutes(windows.AF_INET6)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = luid.FlushIPAddresses(windows.AF_INET6)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = luid.FlushDNS(windows.AF_INET6)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = luid.FlushDNS(windows.AF_INET)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
foundDefault4 := false
|
|
||||||
foundDefault6 := false
|
|
||||||
|
|
||||||
if autoRoute {
|
|
||||||
var (
|
|
||||||
allowedIPs []netip.Prefix
|
|
||||||
|
|
||||||
// add default
|
|
||||||
routeArr = []string{"0.0.0.0/1"}
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, route := range routeArr {
|
|
||||||
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
|
|
||||||
}
|
|
||||||
|
|
||||||
estimatedRouteCount := len(allowedIPs)
|
|
||||||
routes := make(map[winipcfg.RouteData]bool, estimatedRouteCount)
|
|
||||||
|
|
||||||
for _, allowedip := range allowedIPs {
|
|
||||||
route := winipcfg.RouteData{
|
|
||||||
Destination: allowedip.Masked(),
|
|
||||||
Metric: 0,
|
|
||||||
}
|
|
||||||
if allowedip.Addr().Is4() {
|
|
||||||
if allowedip.Bits() == 0 {
|
|
||||||
foundDefault4 = true
|
|
||||||
}
|
|
||||||
route.NextHop = netip.IPv4Unspecified()
|
|
||||||
} else if allowedip.Addr().Is6() {
|
|
||||||
if allowedip.Bits() == 0 {
|
|
||||||
foundDefault6 = true
|
|
||||||
}
|
|
||||||
route.NextHop = netip.IPv6Unspecified()
|
|
||||||
}
|
|
||||||
routes[route] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
|
|
||||||
for route := range routes {
|
|
||||||
r := route
|
|
||||||
deduplicatedRoutes = append(deduplicatedRoutes, &r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add gateway
|
|
||||||
deduplicatedRoutes = append(deduplicatedRoutes, &winipcfg.RouteData{
|
|
||||||
Destination: addr.Masked(),
|
|
||||||
NextHop: gw,
|
|
||||||
Metric: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
err = luid.SetRoutesForFamily(currentFamily, deduplicatedRoutes)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set routes: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
|
||||||
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
|
||||||
cleanupAddressesOnDisconnectedInterfaces(currentFamily, addresses)
|
|
||||||
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
|
||||||
}
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set ips: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipif *winipcfg.MibIPInterfaceRow
|
|
||||||
ipif, err = luid.IPInterface(family4)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ipif.ForwardingEnabled = true
|
|
||||||
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
||||||
ipif.DadTransmits = 0
|
|
||||||
ipif.ManagedAddressConfigurationSupported = false
|
|
||||||
ipif.OtherStatefulConfigurationSupported = false
|
|
||||||
if forceMTU > 0 {
|
|
||||||
ipif.NLMTU = uint32(forceMTU)
|
|
||||||
}
|
|
||||||
if foundDefault4 {
|
|
||||||
ipif.UseAutomaticMetric = false
|
|
||||||
ipif.Metric = 0
|
|
||||||
}
|
|
||||||
err = ipif.Set()
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set metric and MTU: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipif6 *winipcfg.MibIPInterfaceRow
|
|
||||||
ipif6, err = luid.IPInterface(familyV6)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
||||||
ipif6.DadTransmits = 0
|
|
||||||
ipif6.ManagedAddressConfigurationSupported = false
|
|
||||||
ipif6.OtherStatefulConfigurationSupported = false
|
|
||||||
if forceMTU > 0 {
|
|
||||||
ipif6.NLMTU = uint32(forceMTU)
|
|
||||||
}
|
|
||||||
if foundDefault6 {
|
|
||||||
ipif6.UseAutomaticMetric = false
|
|
||||||
ipif6.Metric = 0
|
|
||||||
}
|
|
||||||
err = ipif6.Set()
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set v6 metric and MTU: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = luid.SetDNS(family4, dnsAddress, nil)
|
|
||||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
|
||||||
goto startOver
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("unable to set DNS %s %s: %w", dnsAddress[0].String(), "nil", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wintunInterfaceName = dev.Name()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
|
|
||||||
if len(addresses) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addrHash := make(map[netip.Addr]bool, len(addresses))
|
|
||||||
for i := range addresses {
|
|
||||||
addrHash[addresses[i].Addr()] = true
|
|
||||||
}
|
|
||||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.OperStatus == winipcfg.IfOperStatusUp {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
|
||||||
if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] {
|
|
||||||
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
|
||||||
log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
|
||||||
_ = iface.LUID.DeleteIPAddress(prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
|
||||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get ethernet interface failure. %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var destination netip.Prefix
|
|
||||||
if family == windows.AF_INET {
|
|
||||||
destination = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
|
||||||
} else {
|
|
||||||
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.OperStatus != winipcfg.IfOperStatusUp {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ifname := iface.FriendlyName()
|
|
||||||
|
|
||||||
if wintunInterfaceName == ifname {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
|
||||||
nextHop := nnip.IpToAddr(gatewayAddress.Address.IP())
|
|
||||||
|
|
||||||
if _, err = iface.LUID.Route(destination, nextHop); err == nil {
|
|
||||||
return ifname, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("ethernet interface not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func CleanupRule() {}
|
|
|
@ -1,26 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
// Handler is a TCP/UDP connection handler that implements
|
|
||||||
// HandleTCPConn and HandleUDPConn methods.
|
|
||||||
type Handler interface {
|
|
||||||
HandleTCP(TCPConn)
|
|
||||||
HandleUDP(UDPConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPHandleFunc handles incoming TCP connection.
|
|
||||||
type TCPHandleFunc func(TCPConn)
|
|
||||||
|
|
||||||
// UDPHandleFunc handles incoming UDP connection.
|
|
||||||
type UDPHandleFunc func(UDPConn)
|
|
|
@ -1,148 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
|
||||||
"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/transport/socks5"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.Handler = (*gvHandler)(nil)
|
|
||||||
|
|
||||||
type gvHandler struct {
|
|
||||||
gateway netip.Addr
|
|
||||||
dnsHijack []netip.AddrPort
|
|
||||||
|
|
||||||
tcpIn chan<- C.ConnContext
|
|
||||||
udpIn chan<- *inbound.PacketAdapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
|
||||||
id := tunConn.ID()
|
|
||||||
|
|
||||||
rAddr := &net.TCPAddr{
|
|
||||||
IP: net.IP(id.LocalAddress),
|
|
||||||
Port: int(id.LocalPort),
|
|
||||||
Zone: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
|
||||||
|
|
||||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
|
|
||||||
go func() {
|
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
|
||||||
defer func() {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
_ = tunConn.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if tunConn.SetReadDeadline(time.Now().Add(D.DefaultDnsReadTimeout)) != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
length := uint16(0)
|
|
||||||
if err := binary.Read(tunConn, binary.BigEndian, &length); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if int(length) > len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := io.ReadFull(tunConn, buf[:length])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := D.RelayDnsPacket(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Write(tunConn, binary.BigEndian, uint16(len(msg)))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = tunConn.Write(msg)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gh.tcpIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
|
||||||
id := tunConn.ID()
|
|
||||||
|
|
||||||
rAddr := &net.UDPAddr{
|
|
||||||
IP: net.IP(id.LocalAddress),
|
|
||||||
Port: int(id.LocalPort),
|
|
||||||
Zone: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
|
||||||
|
|
||||||
if rAddrPort.Addr() == gh.gateway {
|
|
||||||
_ = tunConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
|
||||||
|
|
||||||
n, addr, err := tunConn.ReadFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := buf[:n]
|
|
||||||
|
|
||||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
msg, err1 := D.RelayDnsPacket(payload)
|
|
||||||
if err1 != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = tunConn.WriteTo(msg, addr)
|
|
||||||
}()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
gvPacket := &packet{
|
|
||||||
pc: tunConn,
|
|
||||||
rAddr: addr,
|
|
||||||
payload: payload,
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN):
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 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(nicID tcpip.NICID, ep stack.LinkEndpoint) option.Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
if err := s.CreateNICWithOptions(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 NICs.
|
|
||||||
func withPromiscuousMode(nicID tcpip.NICID, v bool) option.Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
if err := s.SetPromiscuousMode(nicID, v); err != nil {
|
|
||||||
return fmt.Errorf("set promiscuous mode: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// withSpoofing sets address spoofing in the given NICs, allowing
|
|
||||||
// endpoints to bind to any address in the NIC.
|
|
||||||
func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
if err := s.SetSpoofing(nicID, v); err != nil {
|
|
||||||
return fmt.Errorf("set spoofing: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,260 +0,0 @@
|
||||||
package option
|
|
||||||
|
|
||||||
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/stack"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 = false
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// tcpMaxBufferSize is the maximum permitted size of a send/recv buffer.
|
|
||||||
tcpMaxBufferSize = pool.RelayBufferSize
|
|
||||||
|
|
||||||
// tcpDefaultBufferSize is the default size of the send buffer for
|
|
||||||
// a transport endpoint.
|
|
||||||
tcpDefaultSendBufferSize = pool.RelayBufferSize
|
|
||||||
|
|
||||||
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
|
|
||||||
// for a transport endpoint.
|
|
||||||
tcpDefaultReceiveBufferSize = pool.RelayBufferSize
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option func(*stack.Stack) error
|
|
||||||
|
|
||||||
// WithDefault sets all default values for stack.
|
|
||||||
func WithDefault() Option {
|
|
||||||
return func(s *stack.Stack) 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
|
|
||||||
WithTCPSendBufferSizeRange(tcpMinBufferSize, tcpDefaultSendBufferSize, tcpMaxBufferSize),
|
|
||||||
WithTCPReceiveBufferSizeRange(tcpMinBufferSize, tcpDefaultReceiveBufferSize, 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 *stack.Stack) 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 *stack.Stack) 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 *stack.Stack) 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 *stack.Stack) error {
|
|
||||||
s.SetICMPLimit(limit)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTCPSendBufferSize sets default the send buffer size for TCP.
|
|
||||||
func WithTCPSendBufferSize(size int) Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
|
|
||||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
|
|
||||||
return fmt.Errorf("set TCP send buffer size range: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTCPSendBufferSizeRange sets the send buffer size range for TCP.
|
|
||||||
func WithTCPSendBufferSizeRange(a, b, c int) Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTCPReceiveBufferSize sets the default receive buffer size for TCP.
|
|
||||||
func WithTCPReceiveBufferSize(size int) Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
|
|
||||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
|
|
||||||
return fmt.Errorf("set TCP receive buffer size range: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTCPReceiveBufferSizeRange sets the receive buffer size range for TCP.
|
|
||||||
func WithTCPReceiveBufferSizeRange(a, b, c int) Option {
|
|
||||||
return func(s *stack.Stack) 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)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTCPCongestionControl sets the current congestion control algorithm.
|
|
||||||
func WithTCPCongestionControl(cc string) Option {
|
|
||||||
return func(s *stack.Stack) 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 *stack.Stack) 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 *stack.Stack) 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 *stack.Stack) 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 *stack.Stack) error {
|
|
||||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &v); err != nil {
|
|
||||||
return fmt.Errorf("set TCP Recovery: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
func withRouteTable(nicID tcpip.NICID) option.Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
s.SetRouteTable([]tcpip.Route{
|
|
||||||
{
|
|
||||||
Destination: header.IPv4EmptySubnet,
|
|
||||||
NIC: nicID,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Destination: header.IPv6EmptySubnet,
|
|
||||||
NIC: nicID,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
// Package gvisor provides a thin wrapper around a gVisor's stack.
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
|
||||||
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gvStack) Close() error {
|
|
||||||
var err error
|
|
||||||
if s.device != nil {
|
|
||||||
err = s.device.Close()
|
|
||||||
}
|
|
||||||
if s.Stack != nil {
|
|
||||||
s.Stack.Close()
|
|
||||||
s.Stack.Wait()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// New allocates a new *gvStack with given options.
|
|
||||||
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.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 := &gvHandler{
|
|
||||||
gateway: tunAddress.Masked().Addr().Next(),
|
|
||||||
dnsHijack: dnsHijack,
|
|
||||||
tcpIn: tcpIn,
|
|
||||||
udpIn: udpIn,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate unique NIC id.
|
|
||||||
nicID := tcpip.NICID(s.Stack.UniqueID())
|
|
||||||
defaultOpts := []option.Option{option.WithDefault()}
|
|
||||||
defaultOpts = append(defaultOpts, opts...)
|
|
||||||
opts = append(defaultOpts,
|
|
||||||
// Create stack NIC and then bind link endpoint to it.
|
|
||||||
withCreatingNIC(nicID, device),
|
|
||||||
|
|
||||||
// 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(nicID, 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(nicID, nicSpoofingEnabled),
|
|
||||||
|
|
||||||
// Add default route table for IPv4 and IPv6. This will handle
|
|
||||||
// all incoming ICMP packets.
|
|
||||||
withRouteTable(nicID),
|
|
||||||
|
|
||||||
// Initiate transport protocol (TCP/UDP) with given handler.
|
|
||||||
withTCPHandler(handler.HandleTCP), withUDPHandler(handler.HandleUDP),
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
if err := opt(s.Stack); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
//go:build no_gvisor
|
|
||||||
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
// New allocates a new *gvStack with given options.
|
|
||||||
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
|
||||||
log.Fatalln("unsupported gvisor stack on the build")
|
|
||||||
return nil, fmt.Errorf("unsupported gvisor stack on the build")
|
|
||||||
}
|
|
|
@ -1,151 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
||||||
"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 = pool.RelayBufferSize
|
|
||||||
|
|
||||||
// maxConnAttempts specifies the maximum number
|
|
||||||
// of in-flight tcp connection attempts.
|
|
||||||
maxConnAttempts = 1 << 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 = 8
|
|
||||||
|
|
||||||
// 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(handle adapter.TCPHandleFunc) option.Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
|
|
||||||
var (
|
|
||||||
wq waiter.Queue
|
|
||||||
ep tcpip.Endpoint
|
|
||||||
err tcpip.Error
|
|
||||||
id = r.ID()
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[STACK] forward tcp request %s:%d->%s:%d: %s", id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Perform a TCP three-way handshake.
|
|
||||||
ep, err = r.CreateEndpoint(&wq)
|
|
||||||
if err != nil {
|
|
||||||
// RST: prevent potential half-open TCP connection leak.
|
|
||||||
r.Complete(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setSocketOptions(s, ep)
|
|
||||||
if err != nil {
|
|
||||||
ep.Close()
|
|
||||||
r.Complete(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer r.Complete(false)
|
|
||||||
|
|
||||||
conn := &tcpConn{
|
|
||||||
TCPConn: gonet.NewTCPConn(&wq, ep),
|
|
||||||
id: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if conn.RemoteAddr() == nil {
|
|
||||||
log.Warnln("[STACK] endpoint is not connected, current state: %v", tcp.EndpointState(ep.State()))
|
|
||||||
_ = conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
handle(conn)
|
|
||||||
})
|
|
||||||
s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
|
|
||||||
{ /* TCP keepalive options */
|
|
||||||
ep.SocketOptions().SetKeepAlive(true)
|
|
||||||
|
|
||||||
idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
|
|
||||||
if err := ep.SetSockOpt(&idle); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
|
|
||||||
if err := ep.SetSockOpt(&interval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ /* TCP recv/send buffer size */
|
|
||||||
var ss tcpip.TCPSendBufferSizeRangeOption
|
|
||||||
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil {
|
|
||||||
ep.SocketOptions().SetReceiveBufferSize(int64(ss.Default), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rs tcpip.TCPReceiveBufferSizeRangeOption
|
|
||||||
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &rs); err == nil {
|
|
||||||
ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpConn struct {
|
|
||||||
*gonet.TCPConn
|
|
||||||
id stack.TransportEndpointID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) ID() *stack.TransportEndpointID {
|
|
||||||
return &c.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) LocalAddr() net.Addr {
|
|
||||||
return &net.TCPAddr{
|
|
||||||
IP: net.IP(c.id.LocalAddress),
|
|
||||||
Port: int(c.id.LocalPort),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) RemoteAddr() net.Addr {
|
|
||||||
return &net.TCPAddr{
|
|
||||||
IP: net.IP(c.id.RemoteAddress),
|
|
||||||
Port: int(c.id.RemotePort),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
//go:build !no_gvisor
|
|
||||||
|
|
||||||
package gvisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"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(handle adapter.UDPHandleFunc) option.Option {
|
|
||||||
return func(s *stack.Stack) error {
|
|
||||||
udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
|
|
||||||
var (
|
|
||||||
wq waiter.Queue
|
|
||||||
id = r.ID()
|
|
||||||
)
|
|
||||||
ep, err := r.CreateEndpoint(&wq)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[STACK] udp forwarder request %s:%d->%s:%d: %s", id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &udpConn{
|
|
||||||
UDPConn: gonet.NewUDPConn(s, &wq, ep),
|
|
||||||
id: id,
|
|
||||||
}
|
|
||||||
handle(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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) LocalAddr() net.Addr {
|
|
||||||
return &net.UDPAddr{
|
|
||||||
IP: net.IP(c.id.LocalAddress),
|
|
||||||
Port: int(c.id.LocalPort),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) RemoteAddr() net.Addr {
|
|
||||||
return &net.UDPAddr{
|
|
||||||
IP: net.IP(c.id.RemoteAddress),
|
|
||||||
Port: int(c.id.RemotePort),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, _ 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,5 +0,0 @@
|
||||||
package ipstack
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
type Stack io.Closer
|
|
|
@ -1,40 +0,0 @@
|
||||||
package mars
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/nat"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StackListener struct {
|
|
||||||
device io.Closer
|
|
||||||
tcp *nat.TCP
|
|
||||||
udp *nat.UDP
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartListener(device io.ReadWriteCloser, gateway, portal, broadcast netip.Addr) (*StackListener, error) {
|
|
||||||
tcp, udp, err := nat.Start(device, gateway, portal, broadcast)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &StackListener{
|
|
||||||
device: device,
|
|
||||||
tcp: tcp,
|
|
||||||
udp: udp,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StackListener) Close() error {
|
|
||||||
_ = t.udp.Close()
|
|
||||||
return t.tcp.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StackListener) TCP() *nat.TCP {
|
|
||||||
return t.tcp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *StackListener) UDP() *nat.UDP {
|
|
||||||
return t.udp
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, *UDP, error) {
|
|
||||||
if !portal.Is4() || !gateway.Is4() {
|
|
||||||
return nil, nil, net.InvalidAddrError("only ipv4 supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
listener, err := net.ListenTCP("tcp4", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tab := newTable()
|
|
||||||
udp := &UDP{
|
|
||||||
device: device,
|
|
||||||
buf: [pool.UDPBufferSize]byte{},
|
|
||||||
}
|
|
||||||
tcp := &TCP{
|
|
||||||
listener: listener,
|
|
||||||
portal: portal,
|
|
||||||
table: tab,
|
|
||||||
}
|
|
||||||
|
|
||||||
gatewayPort := uint16(listener.Addr().(*net.TCPAddr).Port)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
_ = tcp.Close()
|
|
||||||
_ = udp.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
buf := make([]byte, pool.RelayBufferSize)
|
|
||||||
|
|
||||||
for {
|
|
||||||
n, err := device.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("system error:%s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := buf[:n]
|
|
||||||
|
|
||||||
var (
|
|
||||||
ipVersion int
|
|
||||||
ip tcpip.IP
|
|
||||||
)
|
|
||||||
|
|
||||||
ipVersion = tcpip.IPVersion(raw)
|
|
||||||
|
|
||||||
switch ipVersion {
|
|
||||||
case tcpip.IPv4Version:
|
|
||||||
ipv4 := tcpip.IPv4Packet(raw)
|
|
||||||
if !ipv4.Valid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv4.TimeToLive() == 0x00 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv4.Flags()&tcpip.FlagMoreFragment != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv4.FragmentOffset() != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = ipv4
|
|
||||||
case tcpip.IPv6Version:
|
|
||||||
ipv6 := tcpip.IPv6Packet(raw)
|
|
||||||
if !ipv6.Valid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv6.HopLimit() == 0x00 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = ipv6
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationIP := ip.DestinationIP()
|
|
||||||
|
|
||||||
if !destinationIP.IsGlobalUnicast() || destinationIP == broadcast {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ip.Protocol() {
|
|
||||||
case tcpip.TCP:
|
|
||||||
t := tcpip.TCPPacket(ip.Payload())
|
|
||||||
if !t.Valid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if destinationIP == portal {
|
|
||||||
if ip.SourceIP() == gateway && t.SourcePort() == gatewayPort {
|
|
||||||
tup := tab.tupleOf(t.DestinationPort())
|
|
||||||
if tup == zeroTuple {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ip.SetSourceIP(tup.DestinationAddr.Addr())
|
|
||||||
t.SetSourcePort(tup.DestinationAddr.Port())
|
|
||||||
ip.SetDestinationIP(tup.SourceAddr.Addr())
|
|
||||||
t.SetDestinationPort(tup.SourceAddr.Port())
|
|
||||||
|
|
||||||
ip.DecTimeToLive()
|
|
||||||
ip.ResetChecksum()
|
|
||||||
t.ResetChecksum(ip.PseudoSum())
|
|
||||||
|
|
||||||
_, _ = device.Write(raw)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tup := tuple{
|
|
||||||
SourceAddr: netip.AddrPortFrom(ip.SourceIP(), t.SourcePort()),
|
|
||||||
DestinationAddr: netip.AddrPortFrom(destinationIP, t.DestinationPort()),
|
|
||||||
}
|
|
||||||
|
|
||||||
port := tab.portOf(tup)
|
|
||||||
if port == 0 {
|
|
||||||
if t.Flags() != tcpip.TCPSyn {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
port = tab.newConn(tup)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip.SetSourceIP(portal)
|
|
||||||
ip.SetDestinationIP(gateway)
|
|
||||||
t.SetSourcePort(port)
|
|
||||||
t.SetDestinationPort(gatewayPort)
|
|
||||||
|
|
||||||
ip.ResetChecksum()
|
|
||||||
t.ResetChecksum(ip.PseudoSum())
|
|
||||||
|
|
||||||
_, _ = device.Write(raw)
|
|
||||||
}
|
|
||||||
case tcpip.UDP:
|
|
||||||
u := tcpip.UDPPacket(ip.Payload())
|
|
||||||
if !u.Valid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
udp.handleUDPPacket(ip, u)
|
|
||||||
case tcpip.ICMP:
|
|
||||||
i := tcpip.ICMPPacket(ip.Payload())
|
|
||||||
|
|
||||||
if i.Type() != tcpip.ICMPTypePingRequest || i.Code() != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
i.SetType(tcpip.ICMPTypePingResponse)
|
|
||||||
|
|
||||||
ip.SetDestinationIP(ip.SourceIP())
|
|
||||||
ip.SetSourceIP(destinationIP)
|
|
||||||
|
|
||||||
ip.ResetChecksum()
|
|
||||||
i.ResetChecksum()
|
|
||||||
|
|
||||||
_, _ = device.Write(raw)
|
|
||||||
case tcpip.ICMPv6:
|
|
||||||
i := tcpip.ICMPv6Packet(ip.Payload())
|
|
||||||
|
|
||||||
if i.Type() != tcpip.ICMPv6EchoRequest || i.Code() != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
i.SetType(tcpip.ICMPv6EchoReply)
|
|
||||||
|
|
||||||
ip.SetDestinationIP(ip.SourceIP())
|
|
||||||
ip.SetSourceIP(destinationIP)
|
|
||||||
|
|
||||||
ip.ResetChecksum()
|
|
||||||
i.ResetChecksum(ip.PseudoSum())
|
|
||||||
|
|
||||||
_, _ = device.Write(raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return tcp, udp, nil
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/generics/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
portBegin = 30000
|
|
||||||
portLength = 10240
|
|
||||||
)
|
|
||||||
|
|
||||||
var zeroTuple = tuple{}
|
|
||||||
|
|
||||||
type tuple struct {
|
|
||||||
SourceAddr netip.AddrPort
|
|
||||||
DestinationAddr netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
type binding struct {
|
|
||||||
tuple tuple
|
|
||||||
offset uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type table struct {
|
|
||||||
tuples map[tuple]*list.Element[*binding]
|
|
||||||
ports [portLength]*list.Element[*binding]
|
|
||||||
available *list.List[*binding]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) tupleOf(port uint16) tuple {
|
|
||||||
offset := port - portBegin
|
|
||||||
if offset > portLength {
|
|
||||||
return zeroTuple
|
|
||||||
}
|
|
||||||
|
|
||||||
elm := t.ports[offset]
|
|
||||||
|
|
||||||
t.available.MoveToFront(elm)
|
|
||||||
|
|
||||||
return elm.Value.tuple
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) portOf(tuple tuple) uint16 {
|
|
||||||
elm := t.tuples[tuple]
|
|
||||||
if elm == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
t.available.MoveToFront(elm)
|
|
||||||
|
|
||||||
return portBegin + elm.Value.offset
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *table) newConn(tuple tuple) uint16 {
|
|
||||||
elm := t.available.Back()
|
|
||||||
b := elm.Value
|
|
||||||
|
|
||||||
delete(t.tuples, b.tuple)
|
|
||||||
t.tuples[tuple] = elm
|
|
||||||
b.tuple = tuple
|
|
||||||
|
|
||||||
t.available.MoveToFront(elm)
|
|
||||||
|
|
||||||
return portBegin + b.offset
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTable() *table {
|
|
||||||
result := &table{
|
|
||||||
tuples: make(map[tuple]*list.Element[*binding], portLength),
|
|
||||||
ports: [portLength]*list.Element[*binding]{},
|
|
||||||
available: list.New[*binding](),
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx := range result.ports {
|
|
||||||
result.ports[idx] = result.available.PushFront(&binding{
|
|
||||||
tuple: tuple{},
|
|
||||||
offset: uint16(idx),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TCP struct {
|
|
||||||
listener *net.TCPListener
|
|
||||||
portal netip.Addr
|
|
||||||
table *table
|
|
||||||
}
|
|
||||||
|
|
||||||
type conn struct {
|
|
||||||
net.Conn
|
|
||||||
|
|
||||||
tuple tuple
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TCP) Accept() (net.Conn, error) {
|
|
||||||
c, err := t.listener.AcceptTCP()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := c.RemoteAddr().(*net.TCPAddr)
|
|
||||||
tup := t.table.tupleOf(uint16(addr.Port))
|
|
||||||
if !addr.IP.Equal(t.portal.AsSlice()) || tup == zeroTuple {
|
|
||||||
_ = c.Close()
|
|
||||||
|
|
||||||
return nil, net.InvalidAddrError("unknown remote addr")
|
|
||||||
}
|
|
||||||
|
|
||||||
addition(c)
|
|
||||||
|
|
||||||
return &conn{
|
|
||||||
Conn: c,
|
|
||||||
tuple: tup,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TCP) Close() error {
|
|
||||||
return t.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TCP) Addr() net.Addr {
|
|
||||||
return t.listener.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TCP) SetDeadline(time time.Time) error {
|
|
||||||
return t.listener.SetDeadline(time)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) LocalAddr() net.Addr {
|
|
||||||
return &net.TCPAddr{
|
|
||||||
IP: c.tuple.SourceAddr.Addr().AsSlice(),
|
|
||||||
Port: int(c.tuple.SourceAddr.Port()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) RemoteAddr() net.Addr {
|
|
||||||
return &net.TCPAddr{
|
|
||||||
IP: c.tuple.DestinationAddr.Addr().AsSlice(),
|
|
||||||
Port: int(c.tuple.DestinationAddr.Port()),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addition(c *net.TCPConn) {
|
|
||||||
sys, err := c.SyscallConn()
|
|
||||||
if err == nil {
|
|
||||||
_ = sys.Control(func(fd uintptr) {
|
|
||||||
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_NO_CHECK, 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package nat
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
func addition(*net.TCPConn) {}
|
|
|
@ -1,145 +0,0 @@
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/tcpip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type call struct {
|
|
||||||
cond *sync.Cond
|
|
||||||
buf []byte
|
|
||||||
n int
|
|
||||||
source net.Addr
|
|
||||||
destination net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDP struct {
|
|
||||||
closed bool
|
|
||||||
device io.Writer
|
|
||||||
queueLock sync.Mutex
|
|
||||||
queue []*call
|
|
||||||
bufLock sync.Mutex
|
|
||||||
buf [pool.UDPBufferSize]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UDP) ReadFrom(buf []byte) (int, net.Addr, net.Addr, error) {
|
|
||||||
u.queueLock.Lock()
|
|
||||||
defer u.queueLock.Unlock()
|
|
||||||
|
|
||||||
for !u.closed {
|
|
||||||
c := &call{
|
|
||||||
cond: sync.NewCond(&u.queueLock),
|
|
||||||
buf: buf,
|
|
||||||
n: -1,
|
|
||||||
source: nil,
|
|
||||||
destination: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
u.queue = append(u.queue, c)
|
|
||||||
|
|
||||||
c.cond.Wait()
|
|
||||||
|
|
||||||
if c.n >= 0 {
|
|
||||||
return c.n, c.source, c.destination, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, nil, nil, net.ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UDP) WriteTo(buf []byte, local net.Addr, remote net.Addr) (int, error) {
|
|
||||||
if u.closed {
|
|
||||||
return 0, net.ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
u.bufLock.Lock()
|
|
||||||
defer u.bufLock.Unlock()
|
|
||||||
|
|
||||||
if len(buf) > 0xffff {
|
|
||||||
return 0, net.InvalidAddrError("invalid ip version")
|
|
||||||
}
|
|
||||||
|
|
||||||
srcAddr, srcOk := local.(*net.UDPAddr)
|
|
||||||
dstAddr, dstOk := remote.(*net.UDPAddr)
|
|
||||||
if !srcOk || !dstOk {
|
|
||||||
return 0, net.InvalidAddrError("invalid addr")
|
|
||||||
}
|
|
||||||
|
|
||||||
srcAddrPort := netip.AddrPortFrom(nnip.IpToAddr(srcAddr.IP), uint16(srcAddr.Port))
|
|
||||||
dstAddrPort := netip.AddrPortFrom(nnip.IpToAddr(dstAddr.IP), uint16(dstAddr.Port))
|
|
||||||
|
|
||||||
if !srcAddrPort.Addr().Is4() || !dstAddrPort.Addr().Is4() {
|
|
||||||
return 0, net.InvalidAddrError("invalid ip version")
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpip.SetIPv4(u.buf[:])
|
|
||||||
|
|
||||||
ip := tcpip.IPv4Packet(u.buf[:])
|
|
||||||
ip.SetHeaderLen(tcpip.IPv4HeaderSize)
|
|
||||||
ip.SetTotalLength(tcpip.IPv4HeaderSize + tcpip.UDPHeaderSize + uint16(len(buf)))
|
|
||||||
ip.SetTypeOfService(0)
|
|
||||||
ip.SetIdentification(uint16(rand.Uint32()))
|
|
||||||
ip.SetFragmentOffset(0)
|
|
||||||
ip.SetTimeToLive(64)
|
|
||||||
ip.SetProtocol(tcpip.UDP)
|
|
||||||
ip.SetSourceIP(srcAddrPort.Addr())
|
|
||||||
ip.SetDestinationIP(dstAddrPort.Addr())
|
|
||||||
|
|
||||||
udp := tcpip.UDPPacket(ip.Payload())
|
|
||||||
udp.SetLength(tcpip.UDPHeaderSize + uint16(len(buf)))
|
|
||||||
udp.SetSourcePort(srcAddrPort.Port())
|
|
||||||
udp.SetDestinationPort(dstAddrPort.Port())
|
|
||||||
copy(udp.Payload(), buf)
|
|
||||||
|
|
||||||
ip.ResetChecksum()
|
|
||||||
udp.ResetChecksum(ip.PseudoSum())
|
|
||||||
|
|
||||||
return u.device.Write(u.buf[:ip.TotalLen()])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UDP) Close() error {
|
|
||||||
u.queueLock.Lock()
|
|
||||||
defer u.queueLock.Unlock()
|
|
||||||
|
|
||||||
u.closed = true
|
|
||||||
|
|
||||||
for _, c := range u.queue {
|
|
||||||
c.cond.Signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UDP) handleUDPPacket(ip tcpip.IP, pkt tcpip.UDPPacket) {
|
|
||||||
var c *call
|
|
||||||
|
|
||||||
u.queueLock.Lock()
|
|
||||||
|
|
||||||
if len(u.queue) > 0 {
|
|
||||||
idx := len(u.queue) - 1
|
|
||||||
c = u.queue[idx]
|
|
||||||
u.queue = u.queue[:idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
u.queueLock.Unlock()
|
|
||||||
|
|
||||||
if c != nil {
|
|
||||||
c.source = &net.UDPAddr{
|
|
||||||
IP: ip.SourceIP().AsSlice(),
|
|
||||||
Port: int(pkt.SourcePort()),
|
|
||||||
}
|
|
||||||
c.destination = &net.UDPAddr{
|
|
||||||
IP: ip.DestinationIP().AsSlice(),
|
|
||||||
Port: int(pkt.DestinationPort()),
|
|
||||||
}
|
|
||||||
c.n = copy(c.buf, pkt.Payload())
|
|
||||||
c.cond.Signal()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ICMPType = byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ICMPTypePingRequest byte = 0x8
|
|
||||||
ICMPTypePingResponse byte = 0x0
|
|
||||||
)
|
|
||||||
|
|
||||||
type ICMPPacket []byte
|
|
||||||
|
|
||||||
func (p ICMPPacket) Type() ICMPType {
|
|
||||||
return p[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ICMPPacket) SetType(v ICMPType) {
|
|
||||||
p[0] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ICMPPacket) Code() byte {
|
|
||||||
return p[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ICMPPacket) Checksum() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ICMPPacket) SetChecksum(sum [2]byte) {
|
|
||||||
p[2] = sum[0]
|
|
||||||
p[3] = sum[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ICMPPacket) ResetChecksum() {
|
|
||||||
p.SetChecksum(zeroChecksum)
|
|
||||||
p.SetChecksum(Checksum(0, p))
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ICMPv6Packet []byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ICMPv6HeaderSize = 4
|
|
||||||
|
|
||||||
ICMPv6MinimumSize = 8
|
|
||||||
|
|
||||||
ICMPv6PayloadOffset = 8
|
|
||||||
|
|
||||||
ICMPv6EchoMinimumSize = 8
|
|
||||||
|
|
||||||
ICMPv6ErrorHeaderSize = 8
|
|
||||||
|
|
||||||
ICMPv6DstUnreachableMinimumSize = ICMPv6MinimumSize
|
|
||||||
|
|
||||||
ICMPv6PacketTooBigMinimumSize = ICMPv6MinimumSize
|
|
||||||
|
|
||||||
ICMPv6ChecksumOffset = 2
|
|
||||||
|
|
||||||
icmpv6PointerOffset = 4
|
|
||||||
|
|
||||||
icmpv6MTUOffset = 4
|
|
||||||
|
|
||||||
icmpv6IdentOffset = 4
|
|
||||||
|
|
||||||
icmpv6SequenceOffset = 6
|
|
||||||
|
|
||||||
NDPHopLimit = 255
|
|
||||||
)
|
|
||||||
|
|
||||||
type ICMPv6Type byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ICMPv6DstUnreachable ICMPv6Type = 1
|
|
||||||
ICMPv6PacketTooBig ICMPv6Type = 2
|
|
||||||
ICMPv6TimeExceeded ICMPv6Type = 3
|
|
||||||
ICMPv6ParamProblem ICMPv6Type = 4
|
|
||||||
ICMPv6EchoRequest ICMPv6Type = 128
|
|
||||||
ICMPv6EchoReply ICMPv6Type = 129
|
|
||||||
|
|
||||||
ICMPv6RouterSolicit ICMPv6Type = 133
|
|
||||||
ICMPv6RouterAdvert ICMPv6Type = 134
|
|
||||||
ICMPv6NeighborSolicit ICMPv6Type = 135
|
|
||||||
ICMPv6NeighborAdvert ICMPv6Type = 136
|
|
||||||
ICMPv6RedirectMsg ICMPv6Type = 137
|
|
||||||
|
|
||||||
ICMPv6MulticastListenerQuery ICMPv6Type = 130
|
|
||||||
ICMPv6MulticastListenerReport ICMPv6Type = 131
|
|
||||||
ICMPv6MulticastListenerDone ICMPv6Type = 132
|
|
||||||
)
|
|
||||||
|
|
||||||
func (typ ICMPv6Type) IsErrorType() bool {
|
|
||||||
return typ&0x80 == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type ICMPv6Code byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ICMPv6NetworkUnreachable ICMPv6Code = 0
|
|
||||||
ICMPv6Prohibited ICMPv6Code = 1
|
|
||||||
ICMPv6BeyondScope ICMPv6Code = 2
|
|
||||||
ICMPv6AddressUnreachable ICMPv6Code = 3
|
|
||||||
ICMPv6PortUnreachable ICMPv6Code = 4
|
|
||||||
ICMPv6Policy ICMPv6Code = 5
|
|
||||||
ICMPv6RejectRoute ICMPv6Code = 6
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ICMPv6HopLimitExceeded ICMPv6Code = 0
|
|
||||||
ICMPv6ReassemblyTimeout ICMPv6Code = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ICMPv6ErroneousHeader ICMPv6Code = 0
|
|
||||||
|
|
||||||
ICMPv6UnknownHeader ICMPv6Code = 1
|
|
||||||
|
|
||||||
ICMPv6UnknownOption ICMPv6Code = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
const ICMPv6UnusedCode ICMPv6Code = 0
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) Type() ICMPv6Type {
|
|
||||||
return ICMPv6Type(b[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetType(t ICMPv6Type) {
|
|
||||||
b[0] = byte(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) Code() ICMPv6Code {
|
|
||||||
return ICMPv6Code(b[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetCode(c ICMPv6Code) {
|
|
||||||
b[1] = byte(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) TypeSpecific() uint32 {
|
|
||||||
return binary.BigEndian.Uint32(b[icmpv6PointerOffset:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetTypeSpecific(val uint32) {
|
|
||||||
binary.BigEndian.PutUint32(b[icmpv6PointerOffset:], val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) Checksum() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(b[ICMPv6ChecksumOffset:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetChecksum(sum [2]byte) {
|
|
||||||
_ = b[ICMPv6ChecksumOffset+1]
|
|
||||||
b[ICMPv6ChecksumOffset] = sum[0]
|
|
||||||
b[ICMPv6ChecksumOffset+1] = sum[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ICMPv6Packet) SourcePort() uint16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ICMPv6Packet) DestinationPort() uint16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ICMPv6Packet) SetSourcePort(uint16) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ICMPv6Packet) SetDestinationPort(uint16) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) MTU() uint32 {
|
|
||||||
return binary.BigEndian.Uint32(b[icmpv6MTUOffset:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetMTU(mtu uint32) {
|
|
||||||
binary.BigEndian.PutUint32(b[icmpv6MTUOffset:], mtu)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) Ident() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(b[icmpv6IdentOffset:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetIdent(ident uint16) {
|
|
||||||
binary.BigEndian.PutUint16(b[icmpv6IdentOffset:], ident)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) Sequence() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(b[icmpv6SequenceOffset:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) SetSequence(sequence uint16) {
|
|
||||||
binary.BigEndian.PutUint16(b[icmpv6SequenceOffset:], sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) MessageBody() []byte {
|
|
||||||
return b[ICMPv6HeaderSize:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) Payload() []byte {
|
|
||||||
return b[ICMPv6PayloadOffset:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b ICMPv6Packet) ResetChecksum(psum uint32) {
|
|
||||||
b.SetChecksum(zeroChecksum)
|
|
||||||
b.SetChecksum(Checksum(psum, b))
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IPProtocol = byte
|
|
||||||
|
|
||||||
type IP interface {
|
|
||||||
Payload() []byte
|
|
||||||
SourceIP() netip.Addr
|
|
||||||
DestinationIP() netip.Addr
|
|
||||||
SetSourceIP(ip netip.Addr)
|
|
||||||
SetDestinationIP(ip netip.Addr)
|
|
||||||
Protocol() IPProtocol
|
|
||||||
DecTimeToLive()
|
|
||||||
ResetChecksum()
|
|
||||||
PseudoSum() uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPProtocol type
|
|
||||||
const (
|
|
||||||
ICMP IPProtocol = 0x01
|
|
||||||
TCP IPProtocol = 0x06
|
|
||||||
UDP IPProtocol = 0x11
|
|
||||||
ICMPv6 IPProtocol = 0x3a
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FlagDontFragment = 1 << 1
|
|
||||||
FlagMoreFragment = 1 << 2
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IPv4HeaderSize = 20
|
|
||||||
|
|
||||||
IPv4Version = 4
|
|
||||||
|
|
||||||
IPv4OptionsOffset = 20
|
|
||||||
IPv4PacketMinLength = IPv4OptionsOffset
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidLength = errors.New("invalid packet length")
|
|
||||||
ErrInvalidIPVersion = errors.New("invalid ip version")
|
|
||||||
ErrInvalidChecksum = errors.New("invalid checksum")
|
|
||||||
)
|
|
||||||
|
|
||||||
type IPv4Packet []byte
|
|
||||||
|
|
||||||
func (p IPv4Packet) TotalLen() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetTotalLength(length uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p[2:], length)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) HeaderLen() uint16 {
|
|
||||||
return uint16(p[0]&0xf) * 4
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetHeaderLen(length uint16) {
|
|
||||||
p[0] &= 0xF0
|
|
||||||
p[0] |= byte(length / 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) TypeOfService() byte {
|
|
||||||
return p[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetTypeOfService(tos byte) {
|
|
||||||
p[1] = tos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Identification() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetIdentification(id uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p[4:], id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) FragmentOffset() uint16 {
|
|
||||||
return binary.BigEndian.Uint16([]byte{p[6] & 0x7, p[7]}) * 8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetFragmentOffset(offset uint32) {
|
|
||||||
flags := p.Flags()
|
|
||||||
binary.BigEndian.PutUint16(p[6:], uint16(offset/8))
|
|
||||||
p.SetFlags(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) DataLen() uint16 {
|
|
||||||
return p.TotalLen() - p.HeaderLen()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Payload() []byte {
|
|
||||||
return p[p.HeaderLen():p.TotalLen()]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Protocol() IPProtocol {
|
|
||||||
return p[9]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetProtocol(protocol IPProtocol) {
|
|
||||||
p[9] = protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Flags() byte {
|
|
||||||
return p[6] >> 5
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetFlags(flags byte) {
|
|
||||||
p[6] &= 0x1F
|
|
||||||
p[6] |= flags << 5
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SourceIP() netip.Addr {
|
|
||||||
return netip.AddrFrom4([4]byte{p[12], p[13], p[14], p[15]})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetSourceIP(ip netip.Addr) {
|
|
||||||
if ip.Is4() {
|
|
||||||
copy(p[12:16], ip.AsSlice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) DestinationIP() netip.Addr {
|
|
||||||
return netip.AddrFrom4([4]byte{p[16], p[17], p[18], p[19]})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetDestinationIP(ip netip.Addr) {
|
|
||||||
if ip.Is4() {
|
|
||||||
copy(p[16:20], ip.AsSlice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Checksum() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[10:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetChecksum(sum [2]byte) {
|
|
||||||
p[10] = sum[0]
|
|
||||||
p[11] = sum[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) TimeToLive() uint8 {
|
|
||||||
return p[8]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) SetTimeToLive(ttl uint8) {
|
|
||||||
p[8] = ttl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) DecTimeToLive() {
|
|
||||||
p[8] = p[8] - uint8(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) ResetChecksum() {
|
|
||||||
p.SetChecksum(zeroChecksum)
|
|
||||||
p.SetChecksum(Checksum(0, p[:p.HeaderLen()]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PseudoSum for tcp checksum
|
|
||||||
func (p IPv4Packet) PseudoSum() uint32 {
|
|
||||||
sum := Sum(p[12:20])
|
|
||||||
sum += uint32(p.Protocol())
|
|
||||||
sum += uint32(p.DataLen())
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Valid() bool {
|
|
||||||
return len(p) >= IPv4HeaderSize && uint16(len(p)) >= p.TotalLen()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p IPv4Packet) Verify() error {
|
|
||||||
if len(p) < IPv4PacketMinLength {
|
|
||||||
return ErrInvalidLength
|
|
||||||
}
|
|
||||||
|
|
||||||
checksum := []byte{p[10], p[11]}
|
|
||||||
headerLength := uint16(p[0]&0xF) * 4
|
|
||||||
packetLength := binary.BigEndian.Uint16(p[2:])
|
|
||||||
|
|
||||||
if p[0]>>4 != 4 {
|
|
||||||
return ErrInvalidIPVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if uint16(len(p)) < packetLength || packetLength < headerLength {
|
|
||||||
return ErrInvalidLength
|
|
||||||
}
|
|
||||||
|
|
||||||
p[10] = 0
|
|
||||||
p[11] = 0
|
|
||||||
defer copy(p[10:12], checksum)
|
|
||||||
|
|
||||||
answer := Checksum(0, p[:headerLength])
|
|
||||||
|
|
||||||
if answer[0] != checksum[0] || answer[1] != checksum[1] {
|
|
||||||
return ErrInvalidChecksum
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ IP = (*IPv4Packet)(nil)
|
|
|
@ -1,141 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
versTCFL = 0
|
|
||||||
|
|
||||||
IPv6PayloadLenOffset = 4
|
|
||||||
|
|
||||||
IPv6NextHeaderOffset = 6
|
|
||||||
hopLimit = 7
|
|
||||||
v6SrcAddr = 8
|
|
||||||
v6DstAddr = v6SrcAddr + IPv6AddressSize
|
|
||||||
|
|
||||||
IPv6FixedHeaderSize = v6DstAddr + IPv6AddressSize
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
versIHL = 0
|
|
||||||
tos = 1
|
|
||||||
ipVersionShift = 4
|
|
||||||
ipIHLMask = 0x0f
|
|
||||||
IPv4IHLStride = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type IPv6Packet []byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
IPv6MinimumSize = IPv6FixedHeaderSize
|
|
||||||
|
|
||||||
IPv6AddressSize = 16
|
|
||||||
|
|
||||||
IPv6Version = 6
|
|
||||||
|
|
||||||
IPv6MinimumMTU = 1280
|
|
||||||
)
|
|
||||||
|
|
||||||
func (b IPv6Packet) PayloadLength() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(b[IPv6PayloadLenOffset:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) HopLimit() uint8 {
|
|
||||||
return b[hopLimit]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) NextHeader() byte {
|
|
||||||
return b[IPv6NextHeaderOffset]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) Protocol() IPProtocol {
|
|
||||||
return b.NextHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) Payload() []byte {
|
|
||||||
return b[IPv6MinimumSize:][:b.PayloadLength()]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SourceIP() netip.Addr {
|
|
||||||
addr, _ := netip.AddrFromSlice(b[v6SrcAddr:][:IPv6AddressSize])
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) DestinationIP() netip.Addr {
|
|
||||||
addr, _ := netip.AddrFromSlice(b[v6DstAddr:][:IPv6AddressSize])
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (IPv6Packet) Checksum() uint16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) TOS() (uint8, uint32) {
|
|
||||||
v := binary.BigEndian.Uint32(b[versTCFL:])
|
|
||||||
return uint8(v >> 20), v & 0xfffff
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetTOS(t uint8, l uint32) {
|
|
||||||
vtf := (6 << 28) | (uint32(t) << 20) | (l & 0xfffff)
|
|
||||||
binary.BigEndian.PutUint32(b[versTCFL:], vtf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetPayloadLength(payloadLength uint16) {
|
|
||||||
binary.BigEndian.PutUint16(b[IPv6PayloadLenOffset:], payloadLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetSourceIP(addr netip.Addr) {
|
|
||||||
if addr.Is6() {
|
|
||||||
copy(b[v6SrcAddr:][:IPv6AddressSize], addr.AsSlice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetDestinationIP(addr netip.Addr) {
|
|
||||||
if addr.Is6() {
|
|
||||||
copy(b[v6DstAddr:][:IPv6AddressSize], addr.AsSlice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetHopLimit(v uint8) {
|
|
||||||
b[hopLimit] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetNextHeader(v byte) {
|
|
||||||
b[IPv6NextHeaderOffset] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) SetProtocol(p IPProtocol) {
|
|
||||||
b.SetNextHeader(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) DecTimeToLive() {
|
|
||||||
b[hopLimit] = b[hopLimit] - uint8(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (IPv6Packet) SetChecksum(uint16) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (IPv6Packet) ResetChecksum() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) PseudoSum() uint32 {
|
|
||||||
sum := Sum(b[v6SrcAddr:IPv6FixedHeaderSize])
|
|
||||||
sum += uint32(b.Protocol())
|
|
||||||
sum += uint32(b.PayloadLength())
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b IPv6Packet) Valid() bool {
|
|
||||||
return len(b) >= IPv6MinimumSize && len(b) >= int(b.PayloadLength())+IPv6MinimumSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func IPVersion(b []byte) int {
|
|
||||||
if len(b) < versIHL+1 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return int(b[versIHL] >> ipVersionShift)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ IP = (*IPv6Packet)(nil)
|
|
|
@ -1,90 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TCPFin uint16 = 1 << 0
|
|
||||||
TCPSyn uint16 = 1 << 1
|
|
||||||
TCPRst uint16 = 1 << 2
|
|
||||||
TCPPuh uint16 = 1 << 3
|
|
||||||
TCPAck uint16 = 1 << 4
|
|
||||||
TCPUrg uint16 = 1 << 5
|
|
||||||
TCPEce uint16 = 1 << 6
|
|
||||||
TCPEwr uint16 = 1 << 7
|
|
||||||
TCPNs uint16 = 1 << 8
|
|
||||||
)
|
|
||||||
|
|
||||||
const TCPHeaderSize = 20
|
|
||||||
|
|
||||||
type TCPPacket []byte
|
|
||||||
|
|
||||||
func (p TCPPacket) SourcePort() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) SetSourcePort(port uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) DestinationPort() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) SetDestinationPort(port uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p[2:], port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) Flags() uint16 {
|
|
||||||
return uint16(p[13] | (p[12] & 0x1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) Checksum() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[16:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) SetChecksum(sum [2]byte) {
|
|
||||||
p[16] = sum[0]
|
|
||||||
p[17] = sum[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) ResetChecksum(psum uint32) {
|
|
||||||
p.SetChecksum(zeroChecksum)
|
|
||||||
p.SetChecksum(Checksum(psum, p))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) Valid() bool {
|
|
||||||
return len(p) >= TCPHeaderSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p TCPPacket) Verify(sourceAddress net.IP, targetAddress net.IP) error {
|
|
||||||
var checksum [2]byte
|
|
||||||
checksum[0] = p[16]
|
|
||||||
checksum[1] = p[17]
|
|
||||||
|
|
||||||
// reset checksum
|
|
||||||
p[16] = 0
|
|
||||||
p[17] = 0
|
|
||||||
|
|
||||||
// restore checksum
|
|
||||||
defer func() {
|
|
||||||
p[16] = checksum[0]
|
|
||||||
p[17] = checksum[1]
|
|
||||||
}()
|
|
||||||
|
|
||||||
// check checksum
|
|
||||||
s := uint32(0)
|
|
||||||
s += Sum(sourceAddress)
|
|
||||||
s += Sum(targetAddress)
|
|
||||||
s += uint32(TCP)
|
|
||||||
s += uint32(len(p))
|
|
||||||
|
|
||||||
check := Checksum(s, p)
|
|
||||||
if checksum[0] != check[0] || checksum[1] != check[1] {
|
|
||||||
return ErrInvalidChecksum
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
var zeroChecksum = [2]byte{0x00, 0x00}
|
|
||||||
|
|
||||||
var SumFnc = SumCompat
|
|
||||||
|
|
||||||
func Sum(b []byte) uint32 {
|
|
||||||
return SumFnc(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checksum for Internet Protocol family headers
|
|
||||||
func Checksum(sum uint32, b []byte) (answer [2]byte) {
|
|
||||||
sum += Sum(b)
|
|
||||||
sum = (sum >> 16) + (sum & 0xffff)
|
|
||||||
sum += sum >> 16
|
|
||||||
sum = ^sum
|
|
||||||
answer[0] = byte(sum >> 8)
|
|
||||||
answer[1] = byte(sum)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetIPv4(packet []byte) {
|
|
||||||
packet[0] = (packet[0] & 0x0f) | (4 << 4)
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
//go:build !noasm
|
|
||||||
|
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
func sumAsmAvx2(data unsafe.Pointer, length uintptr) uintptr
|
|
||||||
|
|
||||||
func SumAVX2(data []byte) uint32 {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(sumAsmAvx2(unsafe.Pointer(&data[0]), uintptr(len(data))))
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if cpu.X86.HasAVX2 {
|
|
||||||
SumFnc = SumAVX2
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
DATA endian_swap_mask<>+0(SB)/8, $0x607040502030001
|
|
||||||
DATA endian_swap_mask<>+8(SB)/8, $0xE0F0C0D0A0B0809
|
|
||||||
DATA endian_swap_mask<>+16(SB)/8, $0x607040502030001
|
|
||||||
DATA endian_swap_mask<>+24(SB)/8, $0xE0F0C0D0A0B0809
|
|
||||||
GLOBL endian_swap_mask<>(SB), RODATA, $32
|
|
||||||
|
|
||||||
// func sumAsmAvx2(data unsafe.Pointer, length uintptr) uintptr
|
|
||||||
//
|
|
||||||
// args (8 bytes aligned):
|
|
||||||
// data unsafe.Pointer - 8 bytes - 0 offset
|
|
||||||
// length uintptr - 8 bytes - 8 offset
|
|
||||||
// result uintptr - 8 bytes - 16 offset
|
|
||||||
#define PDATA AX
|
|
||||||
#define LENGTH CX
|
|
||||||
#define RESULT BX
|
|
||||||
TEXT ·sumAsmAvx2(SB),NOSPLIT,$0-24
|
|
||||||
MOVQ data+0(FP), PDATA
|
|
||||||
MOVQ length+8(FP), LENGTH
|
|
||||||
XORQ RESULT, RESULT
|
|
||||||
|
|
||||||
#define VSUM Y0
|
|
||||||
#define ENDIAN_SWAP_MASK Y1
|
|
||||||
BEGIN:
|
|
||||||
VMOVDQU endian_swap_mask<>(SB), ENDIAN_SWAP_MASK
|
|
||||||
VPXOR VSUM, VSUM, VSUM
|
|
||||||
|
|
||||||
#define LOADED_0 Y2
|
|
||||||
#define LOADED_1 Y3
|
|
||||||
#define LOADED_2 Y4
|
|
||||||
#define LOADED_3 Y5
|
|
||||||
BATCH_64:
|
|
||||||
CMPQ LENGTH, $64
|
|
||||||
JB BATCH_32
|
|
||||||
VPMOVZXWD (PDATA), LOADED_0
|
|
||||||
VPMOVZXWD 16(PDATA), LOADED_1
|
|
||||||
VPMOVZXWD 32(PDATA), LOADED_2
|
|
||||||
VPMOVZXWD 48(PDATA), LOADED_3
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED_0, LOADED_0
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED_1, LOADED_1
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED_2, LOADED_2
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED_3, LOADED_3
|
|
||||||
VPADDD LOADED_0, VSUM, VSUM
|
|
||||||
VPADDD LOADED_1, VSUM, VSUM
|
|
||||||
VPADDD LOADED_2, VSUM, VSUM
|
|
||||||
VPADDD LOADED_3, VSUM, VSUM
|
|
||||||
ADDQ $-64, LENGTH
|
|
||||||
ADDQ $64, PDATA
|
|
||||||
JMP BATCH_64
|
|
||||||
#undef LOADED_0
|
|
||||||
#undef LOADED_1
|
|
||||||
#undef LOADED_2
|
|
||||||
#undef LOADED_3
|
|
||||||
|
|
||||||
#define LOADED_0 Y2
|
|
||||||
#define LOADED_1 Y3
|
|
||||||
BATCH_32:
|
|
||||||
CMPQ LENGTH, $32
|
|
||||||
JB BATCH_16
|
|
||||||
VPMOVZXWD (PDATA), LOADED_0
|
|
||||||
VPMOVZXWD 16(PDATA), LOADED_1
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED_0, LOADED_0
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED_1, LOADED_1
|
|
||||||
VPADDD LOADED_0, VSUM, VSUM
|
|
||||||
VPADDD LOADED_1, VSUM, VSUM
|
|
||||||
ADDQ $-32, LENGTH
|
|
||||||
ADDQ $32, PDATA
|
|
||||||
JMP BATCH_32
|
|
||||||
#undef LOADED_0
|
|
||||||
#undef LOADED_1
|
|
||||||
|
|
||||||
#define LOADED Y2
|
|
||||||
BATCH_16:
|
|
||||||
CMPQ LENGTH, $16
|
|
||||||
JB COLLECT
|
|
||||||
VPMOVZXWD (PDATA), LOADED
|
|
||||||
VPSHUFB ENDIAN_SWAP_MASK, LOADED, LOADED
|
|
||||||
VPADDD LOADED, VSUM, VSUM
|
|
||||||
ADDQ $-16, LENGTH
|
|
||||||
ADDQ $16, PDATA
|
|
||||||
JMP BATCH_16
|
|
||||||
#undef LOADED
|
|
||||||
|
|
||||||
#define EXTRACTED Y2
|
|
||||||
#define EXTRACTED_128 X2
|
|
||||||
#define TEMP_64 DX
|
|
||||||
COLLECT:
|
|
||||||
VEXTRACTI128 $0, VSUM, EXTRACTED_128
|
|
||||||
VPEXTRD $0, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VPEXTRD $1, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VPEXTRD $2, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VPEXTRD $3, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VEXTRACTI128 $1, VSUM, EXTRACTED_128
|
|
||||||
VPEXTRD $0, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VPEXTRD $1, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VPEXTRD $2, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
VPEXTRD $3, EXTRACTED_128, TEMP_64
|
|
||||||
ADDL TEMP_64, RESULT
|
|
||||||
#undef EXTRACTED
|
|
||||||
#undef EXTRACTED_128
|
|
||||||
#undef TEMP_64
|
|
||||||
|
|
||||||
#define TEMP DX
|
|
||||||
#define TEMP2 SI
|
|
||||||
BATCH_2:
|
|
||||||
CMPQ LENGTH, $2
|
|
||||||
JB BATCH_1
|
|
||||||
XORQ TEMP, TEMP
|
|
||||||
MOVW (PDATA), TEMP
|
|
||||||
MOVQ TEMP, TEMP2
|
|
||||||
SHRW $8, TEMP2
|
|
||||||
SHLW $8, TEMP
|
|
||||||
ORW TEMP2, TEMP
|
|
||||||
ADDL TEMP, RESULT
|
|
||||||
ADDQ $-2, LENGTH
|
|
||||||
ADDQ $2, PDATA
|
|
||||||
JMP BATCH_2
|
|
||||||
#undef TEMP
|
|
||||||
|
|
||||||
#define TEMP DX
|
|
||||||
BATCH_1:
|
|
||||||
CMPQ LENGTH, $0
|
|
||||||
JZ RETURN
|
|
||||||
XORQ TEMP, TEMP
|
|
||||||
MOVB (PDATA), TEMP
|
|
||||||
SHLW $8, TEMP
|
|
||||||
ADDL TEMP, RESULT
|
|
||||||
#undef TEMP
|
|
||||||
|
|
||||||
RETURN:
|
|
||||||
MOVQ RESULT, result+16(FP)
|
|
||||||
RET
|
|
|
@ -1,51 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_SumAVX2(t *testing.T) {
|
|
||||||
if !cpu.X86.HasAVX2 {
|
|
||||||
t.Skipf("AVX2 unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes := make([]byte, chunkSize)
|
|
||||||
|
|
||||||
for size := 0; size <= chunkSize; size++ {
|
|
||||||
for count := 0; count < chunkCount; count++ {
|
|
||||||
_, err := rand.Reader.Read(bytes[:size])
|
|
||||||
if err != nil {
|
|
||||||
t.Skipf("Rand read failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
compat := SumCompat(bytes[:size])
|
|
||||||
avx := SumAVX2(bytes[:size])
|
|
||||||
|
|
||||||
if compat != avx {
|
|
||||||
t.Errorf("Sum of length=%d mismatched", size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_SumAVX2(b *testing.B) {
|
|
||||||
if !cpu.X86.HasAVX2 {
|
|
||||||
b.Skipf("AVX2 unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes := make([]byte, chunkSize)
|
|
||||||
|
|
||||||
_, err := rand.Reader.Read(bytes)
|
|
||||||
if err != nil {
|
|
||||||
b.Skipf("Rand read failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
SumAVX2(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
func sumAsmNeon(data unsafe.Pointer, length uintptr) uintptr
|
|
||||||
|
|
||||||
func SumNeon(data []byte) uint32 {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(sumAsmNeon(unsafe.Pointer(&data[0]), uintptr(len(data))))
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if cpu.ARM64.HasASIMD {
|
|
||||||
SumFnc = SumNeon
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
// func sumAsmNeon(data unsafe.Pointer, length uintptr) uintptr
|
|
||||||
//
|
|
||||||
// args (8 bytes aligned):
|
|
||||||
// data unsafe.Pointer - 8 bytes - 0 offset
|
|
||||||
// length uintptr - 8 bytes - 8 offset
|
|
||||||
// result uintptr - 8 bytes - 16 offset
|
|
||||||
#define PDATA R0
|
|
||||||
#define LENGTH R1
|
|
||||||
#define RESULT R2
|
|
||||||
#define VSUM V0
|
|
||||||
TEXT ·sumAsmNeon(SB),NOSPLIT,$0-24
|
|
||||||
MOVD data+0(FP), PDATA
|
|
||||||
MOVD length+8(FP), LENGTH
|
|
||||||
MOVD $0, RESULT
|
|
||||||
VMOVQ $0, $0, VSUM
|
|
||||||
|
|
||||||
#define LOADED_0 V1
|
|
||||||
#define LOADED_1 V2
|
|
||||||
#define LOADED_2 V3
|
|
||||||
#define LOADED_3 V4
|
|
||||||
BATCH_32:
|
|
||||||
CMP $32, LENGTH
|
|
||||||
BLO BATCH_16
|
|
||||||
VLD1 (PDATA), [LOADED_0.B8, LOADED_1.B8, LOADED_2.B8, LOADED_3.B8]
|
|
||||||
VREV16 LOADED_0.B8, LOADED_0.B8
|
|
||||||
VREV16 LOADED_1.B8, LOADED_1.B8
|
|
||||||
VREV16 LOADED_2.B8, LOADED_2.B8
|
|
||||||
VREV16 LOADED_3.B8, LOADED_3.B8
|
|
||||||
VUSHLL $0, LOADED_0.H4, LOADED_0.S4
|
|
||||||
VUSHLL $0, LOADED_1.H4, LOADED_1.S4
|
|
||||||
VUSHLL $0, LOADED_2.H4, LOADED_2.S4
|
|
||||||
VUSHLL $0, LOADED_3.H4, LOADED_3.S4
|
|
||||||
VADD LOADED_0.S4, VSUM.S4, VSUM.S4
|
|
||||||
VADD LOADED_1.S4, VSUM.S4, VSUM.S4
|
|
||||||
VADD LOADED_2.S4, VSUM.S4, VSUM.S4
|
|
||||||
VADD LOADED_3.S4, VSUM.S4, VSUM.S4
|
|
||||||
ADD $-32, LENGTH
|
|
||||||
ADD $32, PDATA
|
|
||||||
B BATCH_32
|
|
||||||
#undef LOADED_0
|
|
||||||
#undef LOADED_1
|
|
||||||
#undef LOADED_2
|
|
||||||
#undef LOADED_3
|
|
||||||
|
|
||||||
#define LOADED_0 V1
|
|
||||||
#define LOADED_1 V2
|
|
||||||
BATCH_16:
|
|
||||||
CMP $16, LENGTH
|
|
||||||
BLO BATCH_8
|
|
||||||
VLD1 (PDATA), [LOADED_0.B8, LOADED_1.B8]
|
|
||||||
VREV16 LOADED_0.B8, LOADED_0.B8
|
|
||||||
VREV16 LOADED_1.B8, LOADED_1.B8
|
|
||||||
VUSHLL $0, LOADED_0.H4, LOADED_0.S4
|
|
||||||
VUSHLL $0, LOADED_1.H4, LOADED_1.S4
|
|
||||||
VADD LOADED_0.S4, VSUM.S4, VSUM.S4
|
|
||||||
VADD LOADED_1.S4, VSUM.S4, VSUM.S4
|
|
||||||
ADD $-16, LENGTH
|
|
||||||
ADD $16, PDATA
|
|
||||||
B BATCH_16
|
|
||||||
#undef LOADED_0
|
|
||||||
#undef LOADED_1
|
|
||||||
|
|
||||||
#define LOADED_0 V1
|
|
||||||
BATCH_8:
|
|
||||||
CMP $8, LENGTH
|
|
||||||
BLO BATCH_2
|
|
||||||
VLD1 (PDATA), [LOADED_0.B8]
|
|
||||||
VREV16 LOADED_0.B8, LOADED_0.B8
|
|
||||||
VUSHLL $0, LOADED_0.H4, LOADED_0.S4
|
|
||||||
VADD LOADED_0.S4, VSUM.S4, VSUM.S4
|
|
||||||
ADD $-8, LENGTH
|
|
||||||
ADD $8, PDATA
|
|
||||||
B BATCH_8
|
|
||||||
#undef LOADED_0
|
|
||||||
|
|
||||||
#define LOADED_L R3
|
|
||||||
#define LOADED_H R4
|
|
||||||
BATCH_2:
|
|
||||||
CMP $2, LENGTH
|
|
||||||
BLO BATCH_1
|
|
||||||
MOVBU (PDATA), LOADED_H
|
|
||||||
MOVBU 1(PDATA), LOADED_L
|
|
||||||
LSL $8, LOADED_H
|
|
||||||
ORR LOADED_H, LOADED_L, LOADED_L
|
|
||||||
ADD LOADED_L, RESULT, RESULT
|
|
||||||
ADD $2, PDATA
|
|
||||||
ADD $-2, LENGTH
|
|
||||||
B BATCH_2
|
|
||||||
#undef LOADED_H
|
|
||||||
#undef LOADED_L
|
|
||||||
|
|
||||||
#define LOADED R3
|
|
||||||
BATCH_1:
|
|
||||||
CMP $1, LENGTH
|
|
||||||
BLO COLLECT
|
|
||||||
MOVBU (PDATA), LOADED
|
|
||||||
LSL $8, LOADED
|
|
||||||
ADD LOADED, RESULT, RESULT
|
|
||||||
|
|
||||||
#define EXTRACTED R3
|
|
||||||
COLLECT:
|
|
||||||
VMOV VSUM.S[0], EXTRACTED
|
|
||||||
ADD EXTRACTED, RESULT
|
|
||||||
VMOV VSUM.S[1], EXTRACTED
|
|
||||||
ADD EXTRACTED, RESULT
|
|
||||||
VMOV VSUM.S[2], EXTRACTED
|
|
||||||
ADD EXTRACTED, RESULT
|
|
||||||
VMOV VSUM.S[3], EXTRACTED
|
|
||||||
ADD EXTRACTED, RESULT
|
|
||||||
#undef VSUM
|
|
||||||
#undef PDATA
|
|
||||||
#undef LENGTH
|
|
||||||
|
|
||||||
RETURN:
|
|
||||||
MOVD RESULT, result+16(FP)
|
|
||||||
RET
|
|
|
@ -1,51 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_SumNeon(t *testing.T) {
|
|
||||||
if !cpu.ARM64.HasASIMD {
|
|
||||||
t.Skipf("Neon unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes := make([]byte, chunkSize)
|
|
||||||
|
|
||||||
for size := 0; size <= chunkSize; size++ {
|
|
||||||
for count := 0; count < chunkCount; count++ {
|
|
||||||
_, err := rand.Reader.Read(bytes[:size])
|
|
||||||
if err != nil {
|
|
||||||
t.Skipf("Rand read failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
compat := SumCompat(bytes[:size])
|
|
||||||
neon := SumNeon(bytes[:size])
|
|
||||||
|
|
||||||
if compat != neon {
|
|
||||||
t.Errorf("Sum of length=%d mismatched", size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_SumNeon(b *testing.B) {
|
|
||||||
if !cpu.ARM64.HasASIMD {
|
|
||||||
b.Skipf("Neon unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes := make([]byte, chunkSize)
|
|
||||||
|
|
||||||
_, err := rand.Reader.Read(bytes)
|
|
||||||
if err != nil {
|
|
||||||
b.Skipf("Rand read failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
SumNeon(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
func SumCompat(b []byte) (sum uint32) {
|
|
||||||
n := len(b)
|
|
||||||
if n&1 != 0 {
|
|
||||||
n--
|
|
||||||
sum += uint32(b[n]) << 8
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < n; i += 2 {
|
|
||||||
sum += (uint32(b[i]) << 8) | uint32(b[i+1])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
chunkSize = 9000
|
|
||||||
chunkCount = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func Benchmark_SumCompat(b *testing.B) {
|
|
||||||
bytes := make([]byte, chunkSize)
|
|
||||||
|
|
||||||
_, err := rand.Reader.Read(bytes)
|
|
||||||
if err != nil {
|
|
||||||
b.Skipf("Rand read failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
SumCompat(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package tcpip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
const UDPHeaderSize = 8
|
|
||||||
|
|
||||||
type UDPPacket []byte
|
|
||||||
|
|
||||||
func (p UDPPacket) Length() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) SetLength(length uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p[4:], length)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) SourcePort() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) SetSourcePort(port uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) DestinationPort() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) SetDestinationPort(port uint16) {
|
|
||||||
binary.BigEndian.PutUint16(p[2:], port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) Payload() []byte {
|
|
||||||
return p[UDPHeaderSize:p.Length()]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) Checksum() uint16 {
|
|
||||||
return binary.BigEndian.Uint16(p[6:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) SetChecksum(sum [2]byte) {
|
|
||||||
p[6] = sum[0]
|
|
||||||
p[7] = sum[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) ResetChecksum(psum uint32) {
|
|
||||||
p.SetChecksum(zeroChecksum)
|
|
||||||
p.SetChecksum(Checksum(psum, p))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p UDPPacket) Valid() bool {
|
|
||||||
return len(p) >= UDPHeaderSize && uint16(len(p)) >= p.Length()
|
|
||||||
}
|
|
|
@ -1,232 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
|
||||||
"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/listener/tun/ipstack/system/mars"
|
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/nat"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sysStack struct {
|
|
||||||
stack io.Closer
|
|
||||||
device device.Device
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
once sync.Once
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sysStack) Close() error {
|
|
||||||
defer func() {
|
|
||||||
if s.device != nil {
|
|
||||||
_ = s.device.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
s.closed = true
|
|
||||||
|
|
||||||
err := s.stack.Close()
|
|
||||||
|
|
||||||
s.wg.Wait()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
|
||||||
var (
|
|
||||||
gateway = tunAddress.Masked().Addr().Next()
|
|
||||||
portal = gateway.Next()
|
|
||||||
broadcast = nnip.UnMasked(tunAddress)
|
|
||||||
)
|
|
||||||
|
|
||||||
stack, err := mars.StartListener(device, gateway, portal, broadcast)
|
|
||||||
if err != nil {
|
|
||||||
_ = device.Close()
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ipStack := &sysStack{stack: stack, device: device}
|
|
||||||
|
|
||||||
dnsAddr := dnsHijack
|
|
||||||
|
|
||||||
tcp := func() {
|
|
||||||
defer func(tcp *nat.TCP) {
|
|
||||||
_ = tcp.Close()
|
|
||||||
}(stack.TCP())
|
|
||||||
|
|
||||||
for !ipStack.closed {
|
|
||||||
conn, err := stack.TCP().Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("[STACK] accept connection error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lAddr := conn.LocalAddr().(*net.TCPAddr)
|
|
||||||
rAddr := conn.RemoteAddr().(*net.TCPAddr)
|
|
||||||
|
|
||||||
lAddrPort := netip.AddrPortFrom(nnip.IpToAddr(lAddr.IP), uint16(lAddr.Port))
|
|
||||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port))
|
|
||||||
|
|
||||||
if rAddrPort.Addr().IsLoopback() {
|
|
||||||
_ = conn.Close()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if D.ShouldHijackDns(dnsAddr, rAddrPort) {
|
|
||||||
go func() {
|
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
|
||||||
defer func() {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
_ = conn.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if conn.SetReadDeadline(time.Now().Add(C.DefaultTCPTimeout)) != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
length := uint16(0)
|
|
||||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if int(length) > len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := io.ReadFull(conn, buf[:length])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := D.RelayDnsPacket(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = conn.Write(msg)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata := &C.Metadata{
|
|
||||||
NetWork: C.TCP,
|
|
||||||
Type: C.TUN,
|
|
||||||
SrcIP: lAddrPort.Addr(),
|
|
||||||
DstIP: rAddrPort.Addr(),
|
|
||||||
SrcPort: strconv.Itoa(lAddr.Port),
|
|
||||||
DstPort: strconv.Itoa(rAddr.Port),
|
|
||||||
AddrType: C.AtypIPv4,
|
|
||||||
Host: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpIn <- context.NewConnContext(conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipStack.wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
udp := func() {
|
|
||||||
defer func(udp *nat.UDP) {
|
|
||||||
_ = udp.Close()
|
|
||||||
}(stack.UDP())
|
|
||||||
|
|
||||||
for !ipStack.closed {
|
|
||||||
buf := pool.Get(pool.UDPBufferSize)
|
|
||||||
|
|
||||||
n, lRAddr, rRAddr, err := stack.UDP().ReadFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := buf[:n]
|
|
||||||
lAddr := lRAddr.(*net.UDPAddr)
|
|
||||||
rAddr := rRAddr.(*net.UDPAddr)
|
|
||||||
|
|
||||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), uint16(rAddr.Port))
|
|
||||||
|
|
||||||
if rAddrPort.Addr().IsLoopback() || rAddrPort.Addr() == gateway {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if D.ShouldHijackDns(dnsAddr, rAddrPort) {
|
|
||||||
go func() {
|
|
||||||
msg, err := D.RelayDnsPacket(raw)
|
|
||||||
if err != nil {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
|
|
||||||
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pkt := &packet{
|
|
||||||
local: lAddr,
|
|
||||||
data: raw,
|
|
||||||
writeBack: func(b []byte, addr net.Addr) (int, error) {
|
|
||||||
return stack.UDP().WriteTo(b, rAddr, lAddr)
|
|
||||||
},
|
|
||||||
drop: func() {
|
|
||||||
_ = pool.Put(buf)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN):
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ipStack.wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
ipStack.once.Do(func() {
|
|
||||||
ipStack.wg.Add(1)
|
|
||||||
go tcp()
|
|
||||||
|
|
||||||
numUDPWorkers := 4
|
|
||||||
if num := runtime.GOMAXPROCS(0); num > numUDPWorkers {
|
|
||||||
numUDPWorkers = num
|
|
||||||
}
|
|
||||||
for i := 0; i < numUDPWorkers; i++ {
|
|
||||||
ipStack.wg.Add(1)
|
|
||||||
go udp()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return ipStack, nil
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
type packet struct {
|
|
||||||
local *net.UDPAddr
|
|
||||||
data []byte
|
|
||||||
writeBack func(b []byte, addr net.Addr) (int, error)
|
|
||||||
drop func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkt *packet) Data() []byte {
|
|
||||||
return pkt.data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
|
||||||
return pkt.writeBack(b, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkt *packet) Drop() {
|
|
||||||
pkt.drop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pkt *packet) LocalAddr() net.Addr {
|
|
||||||
return pkt.local
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"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/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"
|
|
||||||
"net/netip"
|
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// New TunAdapter
|
|
||||||
func New(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
|
||||||
|
|
||||||
var (
|
|
||||||
tunAddress = tunConf.TunAddressPrefix
|
|
||||||
devName = tunConf.Device
|
|
||||||
stackType = tunConf.Stack
|
|
||||||
autoRoute = tunConf.AutoRoute
|
|
||||||
mtu = 9000
|
|
||||||
|
|
||||||
tunDevice device.Device
|
|
||||||
tunStack ipstack.Stack
|
|
||||||
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if devName == "" {
|
|
||||||
devName = generateDeviceName()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tunAddress.IsValid() || !tunAddress.Addr().Is4() {
|
|
||||||
tunAddress = netip.MustParsePrefix("198.18.0.1/16")
|
|
||||||
}
|
|
||||||
|
|
||||||
// open tun device
|
|
||||||
tunDevice, err = parseDevice(devName, uint32(mtu))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't open tun: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, tunConf.DNSHijack, tunAddress, tcpIn, udpIn)
|
|
||||||
|
|
||||||
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 New system stack: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tunStack, err = system.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn)
|
|
||||||
if err != nil {
|
|
||||||
_ = tunDevice.Close()
|
|
||||||
return nil, fmt.Errorf("can't New system stack: %w", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// never happen
|
|
||||||
}
|
|
||||||
|
|
||||||
// setting address and routing
|
|
||||||
err = commons.ConfigInterfaceAddress(tunDevice, tunAddress, mtu, autoRoute)
|
|
||||||
if err != nil {
|
|
||||||
_ = tunDevice.Close()
|
|
||||||
return nil, fmt.Errorf("setting interface address and routing failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tunConf.AutoDetectInterface {
|
|
||||||
commons.StartDefaultInterfaceChangeMonitor()
|
|
||||||
}
|
|
||||||
|
|
||||||
setAtLatest(stackType, devName)
|
|
||||||
|
|
||||||
log.Infoln("TUN stack listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", tunDevice.Name(), tunAddress.Masked().Addr().Next().String(), mtu, autoRoute, stackType)
|
|
||||||
return tunStack, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateDeviceName() string {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin":
|
|
||||||
return tun.Driver + "://utun"
|
|
||||||
case "windows":
|
|
||||||
return tun.Driver + "://Meta"
|
|
||||||
default:
|
|
||||||
return tun.Driver + "://Meta"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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, devName string) {
|
|
||||||
if stackType != C.TunSystem {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin":
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.inet.ip.forwarding=1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.inet6.ip6.forwarding=1")
|
|
||||||
case "windows":
|
|
||||||
_, _ = cmd.ExecCmd("ipconfig /renew")
|
|
||||||
case "linux":
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.forwarding = 1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_local = 1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.accept_redirects = 1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.rp_filter = 2")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.forwarding = 1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.accept_local = 1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.accept_redirects = 1")
|
|
||||||
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.default.rp_filter = 2")
|
|
||||||
// _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.forwarding = 1", devName))
|
|
||||||
// _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_local = 1", devName))
|
|
||||||
// _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.accept_redirects = 1", devName))
|
|
||||||
// _, _ = cmd.ExecCmd(fmt.Sprintf("sysctl -w net.ipv4.conf.%s.rp_filter = 2", devName))
|
|
||||||
// _, _ = cmd.ExecCmd("iptables -t filter -P FORWARD ACCEPT")
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue