From d7732f6ebc5ad1991e2ae15e7e332f8040c32ad4 Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Thu, 1 Jul 2021 22:49:29 +0800 Subject: [PATCH] Code: refresh code --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/go.yml | 31 +- .github/workflows/stale.yml | 6 +- Makefile | 6 +- README.md | 95 +- adapter/outbound/trojan.go | 14 +- adapter/outbound/vless.go | 326 ++++++ adapter/parser.go | 7 + component/mmdb/mmdb.go | 43 - component/resolver/patch.go | 20 + config/config.go | 20 +- config/initial.go | 59 +- constant/adapters.go | 3 + constant/metadata.go | 9 +- constant/path.go | 12 + constant/rule.go | 4 + dns/filters.go | 38 +- dns/middleware.go | 2 +- dns/patch.go | 16 + dns/server.go | 8 +- go.mod | 4 +- go.sum | 591 ++++++++++- hub/executor/executor.go | 72 +- hub/hub.go | 4 + hub/route/configs.go | 13 + listener/listener.go | 46 + listener/tproxy/tproxy_linux_iptables.go | 192 ++++ listener/tun/dev/dev.go | 66 ++ listener/tun/dev/dev_darwin.go | 506 +++++++++ listener/tun/dev/dev_linux.go | 254 +++++ listener/tun/dev/dev_unsupport.go | 17 + listener/tun/dev/dev_windows.go | 552 ++++++++++ listener/tun/dev/winipcfg/config.go | 56 + .../dev/winipcfg/interface_change_handler.go | 85 ++ listener/tun/dev/winipcfg/luid.go | 383 +++++++ listener/tun/dev/winipcfg/mksyscall.go | 3 + listener/tun/dev/winipcfg/netsh.go | 105 ++ .../tun/dev/winipcfg/route_change_handler.go | 85 ++ listener/tun/dev/winipcfg/types.go | 993 ++++++++++++++++++ listener/tun/dev/winipcfg/types_32.go | 227 ++++ listener/tun/dev/winipcfg/types_64.go | 216 ++++ .../unicast_address_change_handler.go | 85 ++ listener/tun/dev/winipcfg/winipcfg.go | 193 ++++ .../tun/dev/winipcfg/zwinipcfg_windows.go | 350 ++++++ .../tun/dev/wintun/dll_fromfile_windows.go | 49 + .../tun/dev/wintun/dll_fromrsrc_windows.go | 56 + listener/tun/dev/wintun/dll_windows.go | 54 + .../tun/dev/wintun/memmod/memmod_windows.go | 620 +++++++++++ .../dev/wintun/memmod/memmod_windows_32.go | 16 + .../dev/wintun/memmod/memmod_windows_386.go | 8 + .../dev/wintun/memmod/memmod_windows_64.go | 36 + .../dev/wintun/memmod/memmod_windows_amd64.go | 8 + .../dev/wintun/memmod/memmod_windows_arm.go | 8 + .../dev/wintun/memmod/memmod_windows_arm64.go | 8 + .../tun/dev/wintun/memmod/syscall_windows.go | 339 ++++++ .../dev/wintun/memmod/syscall_windows_32.go | 45 + .../dev/wintun/memmod/syscall_windows_64.go | 44 + listener/tun/dev/wintun/session_windows.go | 103 ++ listener/tun/dev/wintun/wintun_windows.go | 221 ++++ listener/tun/ipstack/gvisor/tun.go | 263 +++++ listener/tun/ipstack/gvisor/tundns.go | 280 +++++ listener/tun/ipstack/gvisor/utils.go | 109 ++ listener/tun/ipstack/stack_adapter.go | 9 + listener/tun/ipstack/system/dns.go | 101 ++ listener/tun/ipstack/system/log.go | 21 + listener/tun/ipstack/system/tcp.go | 37 + listener/tun/ipstack/system/tun.go | 125 +++ listener/tun/ipstack/system/udp.go | 74 ++ listener/tun/tun_adapter.go | 51 + log/log.go | 6 + main.go | 8 +- rule/base.go | 13 + rule/domain.go | 8 +- rule/domain_keyword.go | 8 +- rule/domain_suffix.go | 8 +- rule/final.go | 4 + rule/geodata/attr.go | 51 + rule/geodata/geodata.go | 86 ++ rule/geodata/geodataproto.go | 15 + rule/geodata/memconservative/cache.go | 142 +++ rule/geodata/memconservative/decode.go | 105 ++ rule/geodata/memconservative/memc.go | 40 + rule/geodata/router/condition.go | 112 ++ rule/geodata/router/condition_geoip.go | 243 +++++ rule/geodata/router/config.pb.go | 720 +++++++++++++ rule/geodata/router/config.proto | 68 ++ rule/geodata/standard/standard.go | 81 ++ .../strmatcher/ac_automaton_matcher.go | 243 +++++ rule/geodata/strmatcher/domain_matcher.go | 98 ++ rule/geodata/strmatcher/full_matcher.go | 25 + rule/geodata/strmatcher/matchers.go | 52 + rule/geodata/strmatcher/mph_matcher.go | 304 ++++++ rule/geodata/strmatcher/strmatcher.go | 107 ++ rule/geoip.go | 63 +- rule/geosite.go | 82 ++ rule/ipcidr.go | 8 +- rule/parser.go | 21 +- rule/port.go | 8 +- rule/process.go | 23 +- transport/vless/conn.go | 109 ++ transport/vless/vless.go | 61 ++ tunnel/statistic/tracker.go | 6 + tunnel/tunnel.go | 31 +- 104 files changed, 11329 insertions(+), 136 deletions(-) create mode 100644 adapter/outbound/vless.go delete mode 100644 component/mmdb/mmdb.go create mode 100644 component/resolver/patch.go create mode 100644 dns/patch.go create mode 100644 listener/tproxy/tproxy_linux_iptables.go create mode 100644 listener/tun/dev/dev.go create mode 100644 listener/tun/dev/dev_darwin.go create mode 100644 listener/tun/dev/dev_linux.go create mode 100644 listener/tun/dev/dev_unsupport.go create mode 100644 listener/tun/dev/dev_windows.go create mode 100644 listener/tun/dev/winipcfg/config.go create mode 100644 listener/tun/dev/winipcfg/interface_change_handler.go create mode 100644 listener/tun/dev/winipcfg/luid.go create mode 100644 listener/tun/dev/winipcfg/mksyscall.go create mode 100644 listener/tun/dev/winipcfg/netsh.go create mode 100644 listener/tun/dev/winipcfg/route_change_handler.go create mode 100644 listener/tun/dev/winipcfg/types.go create mode 100644 listener/tun/dev/winipcfg/types_32.go create mode 100644 listener/tun/dev/winipcfg/types_64.go create mode 100644 listener/tun/dev/winipcfg/unicast_address_change_handler.go create mode 100644 listener/tun/dev/winipcfg/winipcfg.go create mode 100644 listener/tun/dev/winipcfg/zwinipcfg_windows.go create mode 100644 listener/tun/dev/wintun/dll_fromfile_windows.go create mode 100644 listener/tun/dev/wintun/dll_fromrsrc_windows.go create mode 100644 listener/tun/dev/wintun/dll_windows.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows_32.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows_386.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows_64.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows_amd64.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows_arm.go create mode 100644 listener/tun/dev/wintun/memmod/memmod_windows_arm64.go create mode 100644 listener/tun/dev/wintun/memmod/syscall_windows.go create mode 100644 listener/tun/dev/wintun/memmod/syscall_windows_32.go create mode 100644 listener/tun/dev/wintun/memmod/syscall_windows_64.go create mode 100644 listener/tun/dev/wintun/session_windows.go create mode 100644 listener/tun/dev/wintun/wintun_windows.go create mode 100644 listener/tun/ipstack/gvisor/tun.go create mode 100644 listener/tun/ipstack/gvisor/tundns.go create mode 100644 listener/tun/ipstack/gvisor/utils.go create mode 100644 listener/tun/ipstack/stack_adapter.go create mode 100644 listener/tun/ipstack/system/dns.go create mode 100644 listener/tun/ipstack/system/log.go create mode 100644 listener/tun/ipstack/system/tcp.go create mode 100644 listener/tun/ipstack/system/tun.go create mode 100644 listener/tun/ipstack/system/udp.go create mode 100644 listener/tun/tun_adapter.go create mode 100644 rule/geodata/attr.go create mode 100644 rule/geodata/geodata.go create mode 100644 rule/geodata/geodataproto.go create mode 100644 rule/geodata/memconservative/cache.go create mode 100644 rule/geodata/memconservative/decode.go create mode 100644 rule/geodata/memconservative/memc.go create mode 100644 rule/geodata/router/condition.go create mode 100644 rule/geodata/router/condition_geoip.go create mode 100644 rule/geodata/router/config.pb.go create mode 100644 rule/geodata/router/config.proto create mode 100644 rule/geodata/standard/standard.go create mode 100644 rule/geodata/strmatcher/ac_automaton_matcher.go create mode 100644 rule/geodata/strmatcher/domain_matcher.go create mode 100644 rule/geodata/strmatcher/full_matcher.go create mode 100644 rule/geodata/strmatcher/matchers.go create mode 100644 rule/geodata/strmatcher/mph_matcher.go create mode 100644 rule/geodata/strmatcher/strmatcher.go create mode 100644 rule/geosite.go create mode 100644 transport/vless/conn.go create mode 100644 transport/vless/vless.go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ae1a2793..79850154 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,7 +2,7 @@ name: "CodeQL" on: push: - branches: [ master, dev ] + branches: [ rm ] jobs: analyze: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 002a9cb3..385d232e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,7 +2,7 @@ name: Publish Docker Image on: push: branches: - - dev + - rm tags: - '*' jobs: diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a52be1e9..f819a476 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,5 +1,5 @@ name: Go -on: [push, pull_request] +on: [push] jobs: build: @@ -30,12 +30,24 @@ jobs: staticcheck -- $(go list ./...) - name: Build - if: startsWith(github.ref, 'refs/tags/') + #if: startsWith(github.ref, 'refs/tags/') env: NAME: clash BINDIR: bin run: make -j releases + - name: Prepare upload + run: | + echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV + echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV + + - name: Upload files to Artifacts + uses: actions/upload-artifact@v2 + with: + name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }} + path: | + bin/* + - name: Upload Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') @@ -44,3 +56,18 @@ jobs: with: files: bin/* draft: true + + - name: Delete workflow runs + uses: GitRML/delete-workflow-runs@main + with: + retain_days: 1 + keep_minimum_runs: 2 + + - name: Remove old Releases + uses: dev-drprasad/delete-older-releases@v0.2.0 + if: startsWith(github.ref, 'refs/tags/') && !cancelled() + with: + keep_latest: 1 + delete_tags: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7581fc40..44facad2 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,8 +2,10 @@ name: Mark stale issues and pull requests on: - schedule: - - cron: "30 1 * * *" + branches: + - rm + tags: + - '*' jobs: stale: diff --git a/Makefile b/Makefile index 68651446..8c2de5f2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME=clash BINDIR=bin -VERSION=$(shell git describe --tags || echo "unknown version") +VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F) BUILDTIME=$(shell date -u) GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ @@ -91,7 +91,7 @@ windows-386: windows-amd64: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe - + windows-arm32v7: GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe @@ -109,4 +109,4 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) releases: $(gz_releases) $(zip_releases) clean: - rm $(BINDIR)/* + rm $(BINDIR)/* \ No newline at end of file diff --git a/README.md b/README.md index b948b49d..9b76c70a 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,98 @@ - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Comprehensive HTTP RESTful API controller -## Premium Features - -- TUN mode on macOS, Linux and Windows. [Doc](https://github.com/Dreamacro/clash/wiki/premium-core-features#tun-device) -- Match your tunnel by [Script](https://github.com/Dreamacro/clash/wiki/premium-core-features#script) -- [Rule Provider](https://github.com/Dreamacro/clash/wiki/premium-core-features#rule-providers) - ## Getting Started Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). +## Advanced usage for this fork repository +### TUN configuration +Support macOS Linux and Windows. + +For Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into the System32 directory. +```yaml +# Enable the TUN listener +tun: + enable: true + stack: system # system or gvisor + dns-listen: 0.0.0.0:53 # additional dns server listen on TUN + auto-route: true # auto set global route +``` +### Rules configuration +- Support rule `GEOSITE` +- Support rule `GEOIP` not match condition +- Support `network` condition for all rules + +The `GEOSITE` and `GEOIP` databases via https://github.com/Loyalsoldier/v2ray-rules-dat +```yaml +rules: + # network condition for rules + - DOMAIN-SUFFIX,tabao.com,DIRECT,tcp + - DST-PORT,123,DIRECT,udp + + # rule GEOSITE + - GEOSITE,category-ads-all,REJECT + - GEOSITE,icloud@cn,DIRECT + - GEOSITE,apple@cn,DIRECT + - GEOSITE,microsoft@cn,DIRECT + - GEOSITE,youtube,PROXY + - GEOSITE,geolocation-cn,DIRECT + #- GEOSITE,geolocation-!cn,PROXY + + - GEOIP,private,DIRECT,no-resolve + - GEOIP,cn,DIRECT + + # Not match condition for rule GEOIP + #- GEOIP,!cn,PROXY + + - MATCH,PROXY +``` +### IPTABLES auto-configuration +Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero. + +When `TPROXY` is enabled, the `TUN` must be disabled. +```yaml +# Enable the TPROXY listener +tproxy-port: 9898 +# Disable the TUN listener +tun: + enable: false +``` +Create user give name `clash`, run `$ sudo useradd -M clash` in command line. + +Run Clash by user `clash` as a daemon. + +Create the systemd configuration file at /etc/systemd/system/clash.service: +```shell +[Unit] +Description=Clash daemon, A rule-based proxy in Go. +After=network.target + +[Service] +Type=simple +User=clash +Group=clash +CapabilityBoundingSet=cap_net_admin +AmbientCapabilities=cap_net_admin +Restart=always +ExecStart=/usr/local/bin/clash -d /etc/clash + +[Install] +WantedBy=multi-user.target +``` +Launch clashd on system startup with: +```shell +$ systemctl enable clash +``` +Launch clashd immediately with: +```shell +$ systemctl start clash +``` + +### Display Process name +Add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections` + +To display process name in GUI please use https://yaling888.github.io/yacd/ + ## Premium Release [Release](https://github.com/Dreamacro/clash/releases/tag/premium) diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 5d852735..8b8864f8 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -127,10 +127,10 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) tOption := &trojan.Option{ - Password: option.Password, - ALPN: option.ALPN, - ServerName: option.Server, - SkipCertVerify: option.SkipCertVerify, + Password: option.Password, + ALPN: option.ALPN, + ServerName: option.Server, + //SkipCertVerify: option.SkipCertVerify, ClientSessionCache: getClientSessionCache(), } @@ -159,9 +159,9 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { } tlsConfig := &tls.Config{ - NextProtos: option.ALPN, - MinVersion: tls.VersionTLS12, - InsecureSkipVerify: tOption.SkipCertVerify, + NextProtos: option.ALPN, + MinVersion: tls.VersionTLS12, + //InsecureSkipVerify: tOption.SkipCertVerify, ServerName: tOption.ServerName, ClientSessionCache: getClientSessionCache(), } diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go new file mode 100644 index 00000000..95932f3a --- /dev/null +++ b/adapter/outbound/vless.go @@ -0,0 +1,326 @@ +package outbound + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "strconv" + + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/resolver" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/gun" + "github.com/Dreamacro/clash/transport/vless" + "github.com/Dreamacro/clash/transport/vmess" + "golang.org/x/net/http2" +) + +type Vless struct { + *Base + client *vless.Client + option *VlessOption + + // for gun mux + gunTLSConfig *tls.Config + gunConfig *gun.Config + transport *http2.Transport +} + +type VlessOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + TLS bool `proxy:"tls,omitempty"` + UDP bool `proxy:"udp,omitempty"` + Network string `proxy:"network,omitempty"` + HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` + GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` + WSHeaders map[string]string `proxy:"ws-headers,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + ServerName string `proxy:"servername,omitempty"` +} + +func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + var err error + switch v.option.Network { + case "ws": + host, port, _ := net.SplitHostPort(v.addr) + wsOpts := &vmess.WebsocketConfig{ + Host: host, + Port: port, + Path: v.option.WSPath, + } + + if len(v.option.WSHeaders) != 0 { + header := http.Header{} + for key, value := range v.option.WSHeaders { + header.Add(key, value) + } + wsOpts.Headers = header + } + + if v.option.TLS { + wsOpts.TLS = true + wsOpts.SessionCache = getClientSessionCache() + wsOpts.SkipCertVerify = v.option.SkipCertVerify + wsOpts.ServerName = v.option.ServerName + } + c, err = vmess.StreamWebsocketConn(c, wsOpts) + case "http": + // readability first, so just copy default TLS logic + if v.option.TLS { + host, _, _ := net.SplitHostPort(v.addr) + tlsOpts := &vmess.TLSConfig{ + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + SessionCache: getClientSessionCache(), + } + + if v.option.ServerName != "" { + tlsOpts.Host = v.option.ServerName + } + + c, err = vmess.StreamTLSConn(c, tlsOpts) + if err != nil { + return nil, err + } + } + + host, _, _ := net.SplitHostPort(v.addr) + httpOpts := &vmess.HTTPConfig{ + Host: host, + Method: v.option.HTTPOpts.Method, + Path: v.option.HTTPOpts.Path, + Headers: v.option.HTTPOpts.Headers, + } + + c = vmess.StreamHTTPConn(c, httpOpts) + case "h2": + host, _, _ := net.SplitHostPort(v.addr) + tlsOpts := vmess.TLSConfig{ + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + SessionCache: getClientSessionCache(), + NextProtos: []string{"h2"}, + } + + if v.option.ServerName != "" { + tlsOpts.Host = v.option.ServerName + } + + c, err = vmess.StreamTLSConn(c, &tlsOpts) + if err != nil { + return nil, err + } + + h2Opts := &vmess.H2Config{ + Hosts: v.option.HTTP2Opts.Host, + Path: v.option.HTTP2Opts.Path, + } + + c, err = vmess.StreamH2Conn(c, h2Opts) + case "grpc": + c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) + default: + // handle TLS + if v.option.TLS { + host, _, _ := net.SplitHostPort(v.addr) + tlsOpts := &vmess.TLSConfig{ + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + SessionCache: getClientSessionCache(), + NextProtos: []string{"h2"}, + } + + if v.option.ServerName != "" { + tlsOpts.Host = v.option.ServerName + } + + c, err = vmess.StreamTLSConn(c, tlsOpts) + } + } + + if err != nil { + return nil, err + } + + return v.client.StreamConn(c, parseVlessAddr(metadata)) +} + +// DialContext implements C.ProxyAdapter +func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + // gun transport + if v.transport != nil { + c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig) + if err != nil { + return nil, err + } + defer safeConnClose(c, err) + + c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) + if err != nil { + return nil, err + } + + return NewConn(c, v), nil + } + + c, err := dialer.DialContext(ctx, "tcp", v.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + tcpKeepAlive(c) + defer safeConnClose(c, err) + + c, err = v.StreamConn(c, metadata) + return NewConn(c, v), err +} + +// DialUDP implements C.ProxyAdapter +func (v *Vless) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { + // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return nil, errors.New("can't resolve ip") + } + metadata.DstIP = ip + } + + var c net.Conn + // gun transport + if v.transport != nil { + c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) + if err != nil { + return nil, err + } + defer safeConnClose(c, err) + + c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) + } else { + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) + defer cancel() + c, err = dialer.DialContext(ctx, "tcp", v.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + tcpKeepAlive(c) + defer safeConnClose(c, err) + + c, err = v.StreamConn(c, metadata) + } + + if err != nil { + return nil, fmt.Errorf("new vmess client error: %v", err) + } + + return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil +} + +func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { + var addrType byte + var addr []byte + switch metadata.AddrType { + case C.AtypIPv4: + addrType = byte(vless.AtypIPv4) + addr = make([]byte, net.IPv4len) + copy(addr[:], metadata.DstIP.To4()) + case C.AtypIPv6: + addrType = byte(vless.AtypIPv6) + addr = make([]byte, net.IPv6len) + copy(addr[:], metadata.DstIP.To16()) + case C.AtypDomainName: + addrType = byte(vless.AtypDomainName) + addr = make([]byte, len(metadata.Host)+1) + addr[0] = byte(len(metadata.Host)) + copy(addr[1:], []byte(metadata.Host)) + } + + port, _ := strconv.Atoi(metadata.DstPort) + return &vless.DstAddr{ + UDP: metadata.NetWork == C.UDP, + AddrType: addrType, + Addr: addr, + Port: uint(port), + } +} + +type vlessPacketConn struct { + net.Conn + rAddr net.Addr +} + +func (uc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return uc.Conn.Write(b) +} + +func (uc *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, err := uc.Conn.Read(b) + return n, uc.rAddr, err +} + +func NewVless(option VlessOption) (*Vless, error) { + client, err := vless.NewClient(option.UUID) + if err != nil { + return nil, err + } + + if option.Network != "ws" { + option.TLS = true + option.SkipCertVerify = false + } + + v := &Vless{ + Base: &Base{ + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Vless, + udp: option.UDP, + }, + client: client, + option: &option, + } + + switch option.Network { + case "h2": + if len(option.HTTP2Opts.Host) == 0 { + option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com") + } + case "grpc": + dialFn := func(network, addr string) (net.Conn, error) { + c, err := dialer.DialContext(context.Background(), "tcp", v.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + tcpKeepAlive(c) + return c, nil + } + + gunConfig := &gun.Config{ + ServiceName: v.option.GrpcOpts.GrpcServiceName, + Host: v.option.ServerName, + } + tlsConfig := &tls.Config{ + InsecureSkipVerify: false, + ServerName: v.option.ServerName, + } + + if v.option.ServerName == "" { + host, _, _ := net.SplitHostPort(v.addr) + tlsConfig.ServerName = host + gunConfig.Host = host + } + + v.gunTLSConfig = tlsConfig + v.gunConfig = gunConfig + v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) + } + + return v, nil +} diff --git a/adapter/parser.go b/adapter/parser.go index 62d15225..70f589e4 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -60,6 +60,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { break } proxy, err = outbound.NewVmess(*vmessOption) + case "vless": + vlessOption := &outbound.VlessOption{} + err = decoder.Decode(mapping, vlessOption) + if err != nil { + break + } + proxy, err = outbound.NewVless(*vlessOption) case "snell": snellOption := &outbound.SnellOption{} err = decoder.Decode(mapping, snellOption) diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go deleted file mode 100644 index 08743985..00000000 --- a/component/mmdb/mmdb.go +++ /dev/null @@ -1,43 +0,0 @@ -package mmdb - -import ( - "sync" - - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - - "github.com/oschwald/geoip2-golang" -) - -var mmdb *geoip2.Reader -var once sync.Once - -func LoadFromBytes(buffer []byte) { - once.Do(func() { - var err error - mmdb, err = geoip2.FromBytes(buffer) - if err != nil { - log.Fatalln("Can't load mmdb: %s", err.Error()) - } - }) -} - -func Verify() bool { - instance, err := geoip2.Open(C.Path.MMDB()) - if err == nil { - instance.Close() - } - return err == nil -} - -func Instance() *geoip2.Reader { - once.Do(func() { - var err error - mmdb, err = geoip2.Open(C.Path.MMDB()) - if err != nil { - log.Fatalln("Can't load mmdb: %s", err.Error()) - } - }) - - return mmdb -} diff --git a/component/resolver/patch.go b/component/resolver/patch.go new file mode 100644 index 00000000..55794695 --- /dev/null +++ b/component/resolver/patch.go @@ -0,0 +1,20 @@ +package resolver + +import D "github.com/miekg/dns" + +var ( + DefaultLocalServer LocalServer +) + +type LocalServer interface { + ServeMsg(msg *D.Msg) (*D.Msg, error) +} + +// ServeMsg with a dns.Msg, return resolve dns.Msg +func ServeMsg(msg *D.Msg) (*D.Msg, error) { + if server := DefaultLocalServer; server != nil { + return server.ServeMsg(msg) + } + + return nil, ErrIPNotFound +} diff --git a/config/config.go b/config/config.go index a256d282..8a1d4dbd 100644 --- a/config/config.go +++ b/config/config.go @@ -41,6 +41,7 @@ type Inbound struct { RedirPort int `json:"redir-port"` TProxyPort int `json:"tproxy-port"` MixedPort int `json:"mixed-port"` + Tun Tun `json:"tun"` Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` BindAddress string `json:"bind-address"` @@ -80,12 +81,21 @@ type Profile struct { StoreSelected bool `yaml:"store-selected"` } +// Tun config +type Tun struct { + Enable bool `yaml:"enable" json:"enable"` + Stack string `yaml:"stack" json:"stack"` + DNSListen string `yaml:"dns-listen" json:"dns-listen"` + AutoRoute bool `yaml:"auto-route" json:"auto-route"` +} + // Experimental config type Experimental struct{} // Config is clash config manager type Config struct { General *General + Tun *Tun DNS *DNS Experimental *Experimental Hosts *trie.DomainTrie @@ -137,6 +147,7 @@ type RawConfig struct { ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"` Hosts map[string]string `yaml:"hosts"` DNS RawDNS `yaml:"dns"` + Tun Tun `yaml:"tun"` Experimental Experimental `yaml:"experimental"` Profile Profile `yaml:"profile"` Proxy []map[string]interface{} `yaml:"proxies"` @@ -166,6 +177,12 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, + Tun: Tun{ + Enable: false, + Stack: "system", + DNSListen: "0.0.0.0:53", + AutoRoute: true, + }, DNS: RawDNS{ Enable: false, UseHosts: true, @@ -176,7 +193,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { }, DefaultNameserver: []string{ "114.114.114.114", - "8.8.8.8", + "223.5.5.5", }, }, Profile: Profile{ @@ -252,6 +269,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { RedirPort: cfg.RedirPort, TProxyPort: cfg.TProxyPort, MixedPort: cfg.MixedPort, + Tun: cfg.Tun, AllowLan: cfg.AllowLan, BindAddress: cfg.BindAddress, }, diff --git a/config/initial.go b/config/initial.go index df8452b9..79ae1878 100644 --- a/config/initial.go +++ b/config/initial.go @@ -6,13 +6,12 @@ import ( "net/http" "os" - "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) -func downloadMMDB(path string) (err error) { - resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb") +func downloadGeoIP(path string) (err error) { + resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat") if err != nil { return } @@ -28,23 +27,42 @@ func downloadMMDB(path string) (err error) { return err } -func initMMDB() error { - if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { - log.Infoln("Can't find MMDB, start download") - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't download MMDB: %s", err.Error()) +func downloadGeoSite(path string) (err error) { + resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat") + if err != nil { + return + } + defer resp.Body.Close() + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + + return err +} + +func initGeoIP() error { + if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { + log.Infoln("Can't find GeoIP.dat, start download") + if err := downloadGeoIP(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) } + log.Infoln("Download GeoIP.dat finish") } - if !mmdb.Verify() { - log.Warnln("MMDB invalid, remove and download") - if err := os.Remove(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) - } + return nil +} - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't download MMDB: %s", err.Error()) +func initGeoSite() error { + if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { + log.Infoln("Can't find GeoSite.dat, start download") + if err := downloadGeoSite(C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) } + log.Infoln("Download GeoSite.dat finish") } return nil @@ -70,9 +88,14 @@ func Init(dir string) error { f.Close() } - // initial mmdb - if err := initMMDB(); err != nil { - return fmt.Errorf("can't initial MMDB: %w", err) + // initial GeoIP + if err := initGeoIP(); err != nil { + return fmt.Errorf("can't initial GeoIP: %w", err) + } + + // initial GeoSite + if err := initGeoSite(); err != nil { + return fmt.Errorf("can't initial GeoSite: %w", err) } return nil } diff --git a/constant/adapters.go b/constant/adapters.go index 733d86b9..1d1031d6 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -18,6 +18,7 @@ const ( Socks5 Http Vmess + Vless Trojan Relay @@ -132,6 +133,8 @@ func (at AdapterType) String() string { return "Http" case Vmess: return "Vmess" + case Vless: + return "Vless" case Trojan: return "Trojan" diff --git a/constant/metadata.go b/constant/metadata.go index 93ef406d..0dd39186 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -14,12 +14,14 @@ const ( TCP NetWork = iota UDP + ALLNet HTTP Type = iota HTTPCONNECT SOCKS REDIR TPROXY + TUN ) type NetWork int @@ -27,8 +29,10 @@ type NetWork int func (n NetWork) String() string { if n == TCP { return "tcp" + } else if n == UDP { + return "udp" } - return "udp" + return "all" } func (n NetWork) MarshalJSON() ([]byte, error) { @@ -49,6 +53,8 @@ func (t Type) String() string { return "Redir" case TPROXY: return "TProxy" + case TUN: + return "Tun" default: return "Unknown" } @@ -68,6 +74,7 @@ type Metadata struct { DstPort string `json:"destinationPort"` AddrType int `json:"-"` Host string `json:"host"` + Process string `json:"process"` } func (m *Metadata) RemoteAddress() string { diff --git a/constant/path.go b/constant/path.go index 021721ec..114f7852 100644 --- a/constant/path.go +++ b/constant/path.go @@ -60,3 +60,15 @@ func (p *path) MMDB() string { func (p *path) Cache() string { return P.Join(p.homeDir, ".cache") } + +func (p *path) GeoIP() string { + return P.Join(p.homeDir, "geoip.dat") +} + +func (p *path) GeoSite() string { + return P.Join(p.homeDir, "geosite.dat") +} + +func (p *path) GetAssetLocation(file string) string { + return P.Join(p.homeDir, file) +} diff --git a/constant/rule.go b/constant/rule.go index d7c43416..7e29a37b 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -5,6 +5,7 @@ const ( Domain RuleType = iota DomainSuffix DomainKeyword + GEOSITE GEOIP IPCIDR SrcIPCIDR @@ -24,6 +25,8 @@ func (rt RuleType) String() string { return "DomainSuffix" case DomainKeyword: return "DomainKeyword" + case GEOSITE: + return "GeoSite" case GEOIP: return "GeoIP" case IPCIDR: @@ -49,4 +52,5 @@ type Rule interface { Adapter() string Payload() string ShouldResolveIP() bool + NetWork() NetWork } diff --git a/dns/filters.go b/dns/filters.go index 583883fa..0381036f 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -3,10 +3,15 @@ package dns import ( "net" - "github.com/Dreamacro/clash/component/mmdb" "github.com/Dreamacro/clash/component/trie" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/rule/geodata" + "github.com/Dreamacro/clash/rule/geodata/router" + _ "github.com/Dreamacro/clash/rule/geodata/standard" ) +var geoIPMatcher *router.GeoIPMatcher + type fallbackIPFilter interface { Match(net.IP) bool } @@ -14,8 +19,35 @@ type fallbackIPFilter interface { type geoipFilter struct{} func (gf *geoipFilter) Match(ip net.IP) bool { - record, _ := mmdb.Instance().Country(ip) - return record.Country.IsoCode != "CN" && record.Country.IsoCode != "" + if geoIPMatcher == nil { + countryCode := "cn" + geoLoader, err := geodata.GetGeoDataLoader("standard") + if err != nil { + log.Errorln("[GeoIPFilter] GetGeoDataLoader error: %s", err.Error()) + return false + } + + records, err := geoLoader.LoadGeoIP(countryCode) + if err != nil { + log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error()) + return false + } + + geoIP := &router.GeoIP{ + CountryCode: countryCode, + Cidr: records, + ReverseMatch: false, + } + + geoIPMatcher, err = router.NewGeoIPMatcher(geoIP) + + if err != nil { + log.Errorln("[GeoIPFilter] NewGeoIPMatcher error: %s", err.Error()) + return false + } + } + + return !geoIPMatcher.Match(ip) } type ipnetFilter struct { diff --git a/dns/middleware.go b/dns/middleware.go index 782c0ef0..9931df2f 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -169,7 +169,7 @@ func compose(middlewares []middleware, endpoint handler) handler { return h } -func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler { +func NewHandler(resolver *Resolver, mapper *ResolverEnhancer) handler { middlewares := []middleware{} if resolver.hosts != nil { diff --git a/dns/patch.go b/dns/patch.go new file mode 100644 index 00000000..76974243 --- /dev/null +++ b/dns/patch.go @@ -0,0 +1,16 @@ +package dns + +import D "github.com/miekg/dns" + +type LocalServer struct { + handler handler +} + +// ServeMsg implement resolver.LocalServer ResolveMsg +func (s *LocalServer) ServeMsg(msg *D.Msg) (*D.Msg, error) { + return handlerWithContext(s.handler, msg) +} + +func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer { + return &LocalServer{handler: NewHandler(resolver, mapper)} +} diff --git a/dns/server.go b/dns/server.go index 7abdd4d0..88277476 100644 --- a/dns/server.go +++ b/dns/server.go @@ -43,14 +43,14 @@ func handlerWithContext(handler handler, msg *D.Msg) (*D.Msg, error) { return handler(ctx, msg) } -func (s *Server) setHandler(handler handler) { +func (s *Server) SetHandler(handler handler) { s.handler = handler } func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) error { if addr == address && resolver != nil { - handler := newHandler(resolver, mapper) - server.setHandler(handler) + handler := NewHandler(resolver, mapper) + server.SetHandler(handler) return nil } @@ -81,7 +81,7 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) e } address = addr - handler := newHandler(resolver, mapper) + handler := NewHandler(resolver, mapper) server = &Server{handler: handler} server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} diff --git a/go.mod b/go.mod index 17056c62..6e9c869d 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v4.0.0+incompatible github.com/gorilla/websocket v1.4.2 + github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99 github.com/miekg/dns v1.1.42 - github.com/oschwald/geoip2-golang v1.5.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 go.uber.org/atomic v1.7.0 @@ -18,5 +18,7 @@ require ( golang.org/x/net v0.0.0-20210508051633-16afe75a6701 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 + google.golang.org/protobuf v1.26.0 gopkg.in/yaml.v2 v2.4.0 + gvisor.dev/gvisor v0.0.0-20210519191755-bd7eb2c99ba9 ) diff --git a/go.sum b/go.sum index 58dcc672..1bc7a8cb 100644 --- a/go.sum +++ b/go.sum @@ -1,59 +1,648 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q= github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/cenkalti/backoff v1.1.1-0.20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20191028175130-9e7d5ac5ea55/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE= github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210115211752-39141e76b647/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99 h1:dkEFEnGUg2z/FAPywWr4yfR/sWDQK76qn3J4Y5H2hJs= +github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw= github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s= github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk= github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA= golang.org/x/net v0.0.0-20210508051633-16afe75a6701/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo= golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.36.0-dev.0.20210208035533-9280052d3665/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gvisor.dev/gvisor v0.0.0-20210519191755-bd7eb2c99ba9 h1:7Xn0JTQiWLxAKGI5FCtQH4DVnQ4K7tBZ6hVSgitTZH8= +gvisor.dev/gvisor v0.0.0-20210519191755-bd7eb2c99ba9/go.mod h1:ucHEMlckp+S/YzKEpwwAyGBhAh807Wxq/8Erc6gFxCE= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.1/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs= +k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= +k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= +k8s.io/client-go v0.16.13/go.mod h1:UKvVT4cajC2iN7DCjLgT0KVY/cbY6DGdUCyRiIfws5M= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 81920221..f6565357 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -3,7 +3,11 @@ package executor import ( "fmt" "io/ioutil" + "net" "os" + "runtime" + "strconv" + "strings" "sync" "github.com/Dreamacro/clash/adapter" @@ -20,6 +24,8 @@ import ( "github.com/Dreamacro/clash/dns" P "github.com/Dreamacro/clash/listener" authStore "github.com/Dreamacro/clash/listener/auth" + "github.com/Dreamacro/clash/listener/tproxy" + "github.com/Dreamacro/clash/listener/tun/dev" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" ) @@ -70,13 +76,16 @@ func ApplyConfig(cfg *config.Config, force bool) { defer mux.Unlock() updateUsers(cfg.Users) + updateDNS(cfg.DNS, cfg.General) updateGeneral(cfg.General, force) + log.SetLevel(log.DEBUG) updateProxies(cfg.Proxies, cfg.Providers) updateRules(cfg.Rules) - updateDNS(cfg.DNS) updateHosts(cfg.Hosts) updateExperimental(cfg) updateProfile(cfg) + updateIPTables(cfg.DNS, cfg.General) + log.SetLevel(cfg.General.LogLevel) } func GetGeneral() *config.General { @@ -93,6 +102,7 @@ func GetGeneral() *config.General { RedirPort: ports.RedirPort, TProxyPort: ports.TProxyPort, MixedPort: ports.MixedPort, + Tun: P.Tun(), Authentication: authenticator, AllowLan: P.AllowLan(), BindAddress: P.BindAddress(), @@ -107,7 +117,7 @@ func GetGeneral() *config.General { func updateExperimental(c *config.Config) {} -func updateDNS(c *config.DNS) { +func updateDNS(c *config.DNS, general *config.General) { if !c.Enable { resolver.DefaultResolver = nil resolver.DefaultHostMapper = nil @@ -141,6 +151,9 @@ func updateDNS(c *config.DNS) { resolver.DefaultResolver = r resolver.DefaultHostMapper = m + if general.Tun.Enable && strings.EqualFold(general.Tun.Stack, "system") { + resolver.DefaultLocalServer = dns.NewLocalServer(r, m) + } if err := dns.ReCreateServer(c.Listen, r, m); err != nil { log.Errorln("Start DNS server error: %s", err.Error()) @@ -165,10 +178,24 @@ func updateRules(rules []C.Rule) { } func updateGeneral(general *config.General, force bool) { - log.SetLevel(general.LogLevel) + log.SetLevel(log.DEBUG) tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 + if (general.Tun.Enable || general.TProxyPort != 0) && general.Interface == "" { + autoDetectInterfaceName, err := dev.GetAutoDetectInterface() + if err == nil { + if autoDetectInterfaceName != "" && autoDetectInterfaceName != "" { + general.Interface = autoDetectInterfaceName + log.Infoln("Use auto detect interface: %s", general.Interface) + } else { + log.Debugln("Auto detect interface is empty.") + } + } else { + log.Debugln("Can not find auto detect interface. %s", err.Error()) + } + } + if general.Interface != "" { dialer.DialHook = dialer.DialerWithInterface(general.Interface) dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface) @@ -178,6 +205,7 @@ func updateGeneral(general *config.General, force bool) { } if !force { + log.SetLevel(general.LogLevel) return } @@ -209,6 +237,13 @@ func updateGeneral(general *config.General, force bool) { if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil { log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) } + + if err := P.ReCreateTun(general.Tun, tcpIn, udpIn); err != nil { + log.Errorln("Start Tun interface error: %s", err.Error()) + os.Exit(2) + } + + log.SetLevel(general.LogLevel) } func updateUsers(users []auth.AuthUser) { @@ -253,3 +288,34 @@ func patchSelectGroup(proxies map[string]C.Proxy) { selector.Set(selected) } } + +func updateIPTables(dns *config.DNS, general *config.General) { + if runtime.GOOS != "linux" || dns.Listen == "" || general.TProxyPort == 0 || general.Tun.Enable { + return + } + + _, dnsPortStr, err := net.SplitHostPort(dns.Listen) + if dnsPortStr == "0" || dnsPortStr == "" || err != nil { + return + } + + dnsPort, err := strconv.Atoi(dnsPortStr) + if err != nil { + return + } + + err = tproxy.SetTProxyLinuxIPTables(general.Interface, general.TProxyPort, dnsPort) + + if err != nil { + log.Errorln("Can not setting iptables for TProxy on linux, %s", err.Error()) + os.Exit(2) + } +} + +func CleanUp() { + P.CleanUp() + + if runtime.GOOS == "linux" { + tproxy.CleanUpTProxyLinuxIPTables() + } +} diff --git a/hub/hub.go b/hub/hub.go index 471fdb5e..5b80ab66 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -48,3 +48,7 @@ func Parse(options ...Option) error { executor.ApplyConfig(cfg, true) return nil } + +func CleanUp() { + executor.CleanUp() +} diff --git a/hub/route/configs.go b/hub/route/configs.go index 48cb95ed..1a9ac8ce 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -30,6 +30,7 @@ type configSchema struct { RedirPort *int `json:"redir-port"` TProxyPort *int `json:"tproxy-port"` MixedPort *int `json:"mixed-port"` + Tun *config.Tun `json:"tun"` AllowLan *bool `json:"allow-lan"` BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` @@ -77,6 +78,18 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn) P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn) + if general.Tun != nil { + err := P.ReCreateTun(*general.Tun, nil, nil) + if err == nil { + log.Infoln("Recreate tun success.") + } else { + log.Errorln("Recreate tun failed: %s", err.Error()) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } + } + if general.Mode != nil { tunnel.SetMode(*general.Mode) } diff --git a/listener/listener.go b/listener/listener.go index cf0390e7..3cc38baf 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -3,16 +3,20 @@ package proxy import ( "fmt" "net" + "runtime" "strconv" "sync" "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/listener/http" "github.com/Dreamacro/clash/listener/mixed" "github.com/Dreamacro/clash/listener/redir" "github.com/Dreamacro/clash/listener/socks" "github.com/Dreamacro/clash/listener/tproxy" + "github.com/Dreamacro/clash/listener/tun" + "github.com/Dreamacro/clash/listener/tun/ipstack" "github.com/Dreamacro/clash/log" ) @@ -29,6 +33,7 @@ var ( tproxyUDPListener *tproxy.UDPListener mixedListener *mixed.Listener mixedUDPLister *socks.UDPListener + tunAdapter ipstack.TunAdapter // lock for recreate function socksMux sync.Mutex @@ -36,6 +41,7 @@ var ( redirMux sync.Mutex tproxyMux sync.Mutex mixedMux sync.Mutex + tunMux sync.Mutex ) type Ports struct { @@ -58,6 +64,18 @@ func SetAllowLan(al bool) { allowLan = al } +func Tun() config.Tun { + if tunAdapter == nil { + return config.Tun{} + } + return config.Tun{ + Enable: true, + Stack: tunAdapter.Stack(), + DNSListen: tunAdapter.DNSListen(), + AutoRoute: tunAdapter.AutoRoute(), + } +} + func SetBindAddress(host string) { bindAddress = host } @@ -275,6 +293,25 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P return nil } +func ReCreateTun(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error { + tunMux.Lock() + defer tunMux.Unlock() + + if tunAdapter != nil { + tunAdapter.Close() + tunAdapter = nil + } + + if !conf.Enable { + return nil + } + + var err error + tunAdapter, err = tun.New(conf, tcpIn, udpIn) + + return err +} + // GetPorts return the ports of proxy servers func GetPorts() *Ports { ports := &Ports{} @@ -330,3 +367,12 @@ func genAddr(host string, port int, allowLan bool) string { return fmt.Sprintf("127.0.0.1:%d", port) } + +// CleanUp clean up something +func CleanUp() { + if runtime.GOOS == "windows" { + if tunAdapter != nil { + tunAdapter.Close() + } + } +} diff --git a/listener/tproxy/tproxy_linux_iptables.go b/listener/tproxy/tproxy_linux_iptables.go new file mode 100644 index 00000000..68b86fb2 --- /dev/null +++ b/listener/tproxy/tproxy_linux_iptables.go @@ -0,0 +1,192 @@ +package tproxy + +import ( + "errors" + "fmt" + "os/exec" + U "os/user" + "runtime" + "strings" + + "github.com/Dreamacro/clash/log" +) + +var ( + interfaceName = "" + tproxyPort = 0 + dnsPort = 0 +) + +const ( + PROXY_FWMARK = "0x2d0" + PROXY_ROUTE_TABLE = "0x2d0" + USERNAME = "clash" +) + +func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error { + var err error + if _, err = execCmd("iptables -V"); err != nil { + return fmt.Errorf("current operations system [%s] are not support iptables or command iptables does not exist", runtime.GOOS) + } + + //if _, err = execCmd("modprobe xt_TPROXY"); err != nil { + // return errors.New("xt_TPROXY module does not exist, please install it") + //} + + user, err := U.Lookup(USERNAME) + if err != nil { + return fmt.Errorf("the user \" %s\" does not exist, please create it", USERNAME) + } + + if ifname == "" { + return errors.New("interface name can not be empty") + } + + ownerUid := user.Uid + + interfaceName = ifname + tproxyPort = tport + dnsPort = dport + + // add route + execCmd(fmt.Sprintf("ip -f inet rule add fwmark %s lookup %s", PROXY_FWMARK, PROXY_ROUTE_TABLE)) + execCmd(fmt.Sprintf("ip -f inet route add local default dev %s table %s", interfaceName, PROXY_ROUTE_TABLE)) + + // set FORWARD + execCmd("sysctl -w net.ipv4.ip_forward=1") + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -o %s -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -o %s -j ACCEPT", interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s ! -o %s -j ACCEPT", interfaceName, interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) + + // set clash divert + execCmd("iptables -t mangle -N clash_divert") + execCmd("iptables -t mangle -F clash_divert") + execCmd(fmt.Sprintf("iptables -t mangle -A clash_divert -j MARK --set-mark %s", PROXY_FWMARK)) + execCmd("iptables -t mangle -A clash_divert -j ACCEPT") + + // set pre routing + execCmd("iptables -t mangle -N clash_prerouting") + execCmd("iptables -t mangle -F clash_prerouting") + execCmd("iptables -t mangle -A clash_prerouting -s 172.17.0.0/16 -j RETURN") + execCmd("iptables -t mangle -A clash_prerouting -p udp --dport 53 -j ACCEPT") + execCmd("iptables -t mangle -A clash_prerouting -p tcp --dport 53 -j ACCEPT") + execCmd("iptables -t mangle -A clash_prerouting -m addrtype --dst-type LOCAL -j RETURN") + addLocalnetworkToChain("clash_prerouting") + execCmd("iptables -t mangle -A clash_prerouting -p tcp -m socket -j clash_divert") + execCmd("iptables -t mangle -A clash_prerouting -p udp -m socket -j clash_divert") + execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p tcp -j TPROXY --on-port %d --tproxy-mark %s/%s", tproxyPort, PROXY_FWMARK, PROXY_FWMARK)) + execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tproxyPort, PROXY_FWMARK, PROXY_FWMARK)) + execCmd("iptables -t mangle -A PREROUTING -j clash_prerouting") + + execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) + execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) + + // set post routing + execCmd(fmt.Sprintf("iptables -t nat -A POSTROUTING -o %s -m addrtype ! --src-type LOCAL -j MASQUERADE", interfaceName)) + + // set output + execCmd("iptables -t mangle -N clash_output") + execCmd("iptables -t mangle -F clash_output") + execCmd(fmt.Sprintf("iptables -t mangle -A clash_output -m owner --uid-owner %s -j RETURN", ownerUid)) + execCmd("iptables -t mangle -A clash_output -p udp -m multiport --dports 53,123,137 -j ACCEPT") + execCmd("iptables -t mangle -A clash_output -p tcp --dport 53 -j ACCEPT") + execCmd("iptables -t mangle -A clash_output -m addrtype --dst-type LOCAL -j RETURN") + execCmd("iptables -t mangle -A clash_output -m addrtype --dst-type BROADCAST -j RETURN") + addLocalnetworkToChain("clash_output") + execCmd(fmt.Sprintf("iptables -t mangle -A clash_output -p tcp -j MARK --set-mark %s", PROXY_FWMARK)) + execCmd(fmt.Sprintf("iptables -t mangle -A clash_output -p udp -j MARK --set-mark %s", PROXY_FWMARK)) + execCmd(fmt.Sprintf("iptables -t mangle -I OUTPUT -o %s -j clash_output", interfaceName)) + + // set dns output + execCmd("iptables -t nat -N clash_dns_output") + execCmd("iptables -t nat -F clash_dns_output") + execCmd(fmt.Sprintf("iptables -t nat -A clash_dns_output -m owner --uid-owner %s -j RETURN", ownerUid)) + execCmd("iptables -t nat -A clash_dns_output -s 172.17.0.0/16 -j RETURN") + execCmd(fmt.Sprintf("iptables -t nat -A clash_dns_output -p udp -j REDIRECT --to-ports %d", dnsPort)) + execCmd(fmt.Sprintf("iptables -t nat -A clash_dns_output -p tcp -j REDIRECT --to-ports %d", dnsPort)) + execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j clash_dns_output") + execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j clash_dns_output") + + log.Infoln("[TProxy] Setting iptables completed") + return nil +} + +func CleanUpTProxyLinuxIPTables() { + + if interfaceName == "" || tproxyPort == 0 || dnsPort == 0 { + return + } + + log.Warnln("Clean up tproxy linux iptables") + + if _, err := execCmd("iptables -t mangle -L clash_divert"); err != nil { + return + } + + // clean route + execCmd(fmt.Sprintf("ip -f inet rule del fwmark %s lookup %s", PROXY_FWMARK, PROXY_ROUTE_TABLE)) + execCmd(fmt.Sprintf("ip -f inet route del local default dev %s table %s", interfaceName, PROXY_ROUTE_TABLE)) + + // clean FORWARD + //execCmd("sysctl -w net.ipv4.ip_forward=0") + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -i %s ! -o %s -j ACCEPT", interfaceName, interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -o %s -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -o %s -j ACCEPT", interfaceName)) + + // clean PREROUTING + execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) + execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) + execCmd("iptables -t mangle -D PREROUTING -j clash_prerouting") + + // clean POSTROUTING + execCmd(fmt.Sprintf("iptables -t nat -D POSTROUTING -o %s -m addrtype ! --src-type LOCAL -j MASQUERADE", interfaceName)) + + // clean OUTPUT + execCmd(fmt.Sprintf("iptables -t mangle -D OUTPUT -o %s -j clash_output", interfaceName)) + execCmd("iptables -t nat -D OUTPUT -p tcp --dport 53 -j clash_dns_output") + execCmd("iptables -t nat -D OUTPUT -p udp --dport 53 -j clash_dns_output") + + // clean chain + execCmd("iptables -t mangle -F clash_prerouting") + execCmd("iptables -t mangle -X clash_prerouting") + execCmd("iptables -t mangle -F clash_divert") + execCmd("iptables -t mangle -X clash_divert") + execCmd("iptables -t mangle -F clash_output") + execCmd("iptables -t mangle -X clash_output") + execCmd("iptables -t nat -F clash_dns_output") + execCmd("iptables -t nat -X clash_dns_output") +} + +func addLocalnetworkToChain(chain string) { + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 0.0.0.0/8 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 10.0.0.0/8 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 100.64.0.0/10 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 127.0.0.0/8 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 169.254.0.0/16 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 172.16.0.0/12 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.0.0/24 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.2.0/24 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.88.99.0/24 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.168.0.0/16 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 198.51.100.0/24 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 203.0.113.0/24 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 224.0.0.0/4 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 240.0.0.0/4 -j RETURN", chain)) + execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 255.255.255.255/32 -j RETURN", chain)) +} + +func execCmd(cmdstr string) (string, error) { + log.Debugln("[TProxy] %s", cmdstr) + + args := strings.Split(cmdstr, " ") + cmd := exec.Command(args[0], args[1:]...) + out, err := cmd.CombinedOutput() + if err != nil { + log.Errorln("[TProxy] error: %s, %s", err.Error(), string(out)) + return "", err + } + + return string(out), nil +} diff --git a/listener/tun/dev/dev.go b/listener/tun/dev/dev.go new file mode 100644 index 00000000..6a5afd31 --- /dev/null +++ b/listener/tun/dev/dev.go @@ -0,0 +1,66 @@ +package dev + +import ( + "os/exec" + "runtime" + + "github.com/Dreamacro/clash/log" +) + +// TunDevice is cross-platform tun interface +type TunDevice interface { + Name() string + URL() string + MTU() (int, error) + IsClose() bool + Close() error + Read(buff []byte) (int, error) + Write(buff []byte) (int, error) +} + +func SetLinuxAutoRoute() { + log.Infoln("Tun adapter auto setting MacOS route") + addLinuxSystemRoute("1") + addLinuxSystemRoute("2/7") + addLinuxSystemRoute("4/6") + addLinuxSystemRoute("8/5") + addLinuxSystemRoute("16/4") + addLinuxSystemRoute("32/3") + addLinuxSystemRoute("64/2") + addLinuxSystemRoute("128.0/1") + addLinuxSystemRoute("198.18.0/16") +} + +func RemoveLinuxAutoRoute() { + log.Infoln("Tun adapter removing MacOS route") + delLinuxSystemRoute("1") + delLinuxSystemRoute("2/7") + delLinuxSystemRoute("4/6") + delLinuxSystemRoute("8/5") + delLinuxSystemRoute("16/4") + delLinuxSystemRoute("32/3") + delLinuxSystemRoute("64/2") + delLinuxSystemRoute("128.0/1") + delLinuxSystemRoute("198.18.0/16") +} + +func addLinuxSystemRoute(net string) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + return + } + cmd := exec.Command("route", "add", "-net", net, "198.18.0.1") + if err := cmd.Run(); err != nil { + log.Errorln("[MacOS auto route] Failed to add system route: %s, cmd: %s", err.Error(), cmd.String()) + } +} + +func delLinuxSystemRoute(net string) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + return + } + cmd := exec.Command("route", "delete", "-net", net, "198.18.0.1") + _ = cmd.Run() + //if err := cmd.Run(); err != nil { + // log.Errorln("[MacOS auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String()) + //} +} diff --git a/listener/tun/dev/dev_darwin.go b/listener/tun/dev/dev_darwin.go new file mode 100644 index 00000000..180f79a4 --- /dev/null +++ b/listener/tun/dev/dev_darwin.go @@ -0,0 +1,506 @@ +// +build darwin + +package dev + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "os/exec" + "sync" + "syscall" + "unsafe" + + "golang.org/x/net/ipv6" + "golang.org/x/sys/unix" + + "github.com/Dreamacro/clash/common/pool" +) + +const utunControlName = "com.apple.net.utun_control" +const _IOC_OUT = 0x40000000 +const _IOC_IN = 0x80000000 +const _IOC_INOUT = _IOC_IN | _IOC_OUT + +// _CTLIOCGINFO value derived from /usr/include/sys/{kern_control,ioccom}.h +// https://github.com/apple/darwin-xnu/blob/master/bsd/sys/ioccom.h + +// #define CTLIOCGINFO _IOWR('N', 3, struct ctl_info) /* get id from name */ = 0xc0644e03 +const _CTLIOCGINFO = _IOC_INOUT | ((100 & 0x1fff) << 16) | uint32(byte('N'))<<8 | 3 + +// #define SIOCAIFADDR_IN6 _IOW('i', 26, struct in6_aliasreq) = 0x8080691a +//const _SIOCAIFADDR_IN6 = _IOC_IN | ((128 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 26 + +// #define SIOCPROTOATTACH_IN6 _IOWR('i', 110, struct in6_aliasreq_64) +const _SIOCPROTOATTACH_IN6 = _IOC_INOUT | ((128 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 110 + +// #define SIOCLL_START _IOWR('i', 130, struct in6_aliasreq) +const _SIOCLL_START = _IOC_INOUT | ((128 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 130 + +// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/nd6.h#L469 +const ND6_INFINITE_LIFETIME = 0xffffffff + +// Following the wireguard-go solution: +// These unix.SYS_* constants were removed from golang.org/x/sys/unix +// so copy them here for now. +// See https://github.com/golang/go/issues/41868 +const ( + sys_IOCTL = 54 + sys_CONNECT = 98 + sys_GETSOCKOPT = 118 +) + +type tunDarwin struct { + //url string + name string + tunAddress string + autoRoute bool + tunFile *os.File + errors chan error + + closed bool + stopOnce sync.Once +} + +// sockaddr_ctl specifeid in /usr/include/sys/kern_control.h +type sockaddrCtl struct { + scLen uint8 + scFamily uint8 + ssSysaddr uint16 + scID uint32 + scUnit uint32 + scReserved [5]uint32 +} + +// https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/net/if.h#L402-L563 + +//type ifreqAddr struct { +// Name [unix.IFNAMSIZ]byte +// Addr unix.RawSockaddrInet4 +// Pad [8]byte +//} + +var sockaddrCtlSize uintptr = 32 + +// OpenTunDevice return a TunDevice according a URL +func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { + + name := "utun" + // TODO: configure the MTU + mtu := 9000 + + ifIndex := -1 + if name != "utun" { + _, err := fmt.Sscanf(name, "utun%d", &ifIndex) + if err != nil || ifIndex < 0 { + return nil, fmt.Errorf("interface name must be utun[0-9]*") + } + } + + fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2) + + if err != nil { + return nil, err + } + + var ctlInfo = &struct { + ctlID uint32 + ctlName [96]byte + }{} + + copy(ctlInfo.ctlName[:], []byte(utunControlName)) + + _, _, errno := unix.Syscall( + sys_IOCTL, + uintptr(fd), + uintptr(_CTLIOCGINFO), + uintptr(unsafe.Pointer(ctlInfo)), + ) + + if errno != 0 { + return nil, fmt.Errorf("_CTLIOCGINFO: %v", errno) + } + + sc := sockaddrCtl{ + scLen: uint8(sockaddrCtlSize), + scFamily: unix.AF_SYSTEM, + ssSysaddr: 2, + scID: ctlInfo.ctlID, + scUnit: uint32(ifIndex) + 1, + } + + scPointer := unsafe.Pointer(&sc) + + _, _, errno = unix.RawSyscall( + sys_CONNECT, + uintptr(fd), + uintptr(scPointer), + uintptr(sockaddrCtlSize), + ) + + if errno != 0 { + return nil, fmt.Errorf("SYS_CONNECT: %v", errno) + } + + err = syscall.SetNonblock(fd, true) + if err != nil { + return nil, err + } + + tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), ""), mtu, tunAddress, autoRoute) + + if err != nil { + return nil, err + } + + if autoRoute { + SetLinuxAutoRoute() + } + + return tun, nil +} + +func CreateTUNFromFile(file *os.File, mtu int, tunAddress string, autoRoute bool) (TunDevice, error) { + tun := &tunDarwin{ + tunFile: file, + tunAddress: tunAddress, + autoRoute: autoRoute, + errors: make(chan error, 5), + } + + name, err := tun.getName() + if err != nil { + tun.tunFile.Close() + return nil, err + } + tun.name = name + + if err != nil { + tun.tunFile.Close() + return nil, err + } + + if mtu > 0 { + err = tun.setMTU(mtu) + if err != nil { + tun.Close() + return nil, err + } + } + + // This address doesn't mean anything here. NIC just net an IP address to set route upon. + // TODO: maybe let user config it. And I'm doubt whether we really need it. + p2pAddress := net.ParseIP("198.18.0.1") + err = tun.setTunAddress(p2pAddress) + if err != nil { + tun.Close() + return nil, err + } + err = tun.attachLinkLocal() + if err != nil { + tun.Close() + return nil, err + } + + return tun, nil +} + +func (t *tunDarwin) Name() string { + return t.name +} + +func (t *tunDarwin) URL() string { + return fmt.Sprintf("dev://%s", t.Name()) +} + +func (t *tunDarwin) MTU() (int, error) { + return t.getInterfaceMtu() +} + +func (t *tunDarwin) Read(buff []byte) (int, error) { + select { + case err := <-t.errors: + return 0, err + default: + n, err := t.tunFile.Read(buff) + if n < 4 { + return 0, err + } + + copy(buff[:], buff[4:]) + return n - 4, err + } +} + +func (t *tunDarwin) Write(buff []byte) (int, error) { + // reserve space for header + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf[:cap(buf)]) + + buf[0] = 0x00 + buf[1] = 0x00 + buf[2] = 0x00 + + copy(buf[4:], buff) + if buf[4]>>4 == ipv6.Version { + buf[3] = unix.AF_INET6 + } else { + buf[3] = unix.AF_INET + } + + // write + return t.tunFile.Write(buf[:4+len(buff)]) +} + +func (t *tunDarwin) IsClose() bool { + return t.closed +} + +func (t *tunDarwin) Close() error { + t.stopOnce.Do(func() { + if t.autoRoute { + RemoveLinuxAutoRoute() + } + t.closed = true + t.tunFile.Close() + }) + return nil +} + +func (t *tunDarwin) getInterfaceMtu() (int, error) { + + // open datagram socket + + fd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + + if err != nil { + return 0, err + } + + defer unix.Close(fd) + + // do ioctl call + + var ifr [64]byte + copy(ifr[:], t.name) + _, _, errno := unix.Syscall( + sys_IOCTL, + uintptr(fd), + uintptr(unix.SIOCGIFMTU), + uintptr(unsafe.Pointer(&ifr[0])), + ) + if errno != 0 { + return 0, fmt.Errorf("failed to get MTU on %s", t.name) + } + + return int(*(*int32)(unsafe.Pointer(&ifr[16]))), nil +} + +func (t *tunDarwin) getName() (string, error) { + var ifName struct { + name [16]byte + } + ifNameSize := uintptr(16) + + var errno syscall.Errno + t.operateOnFd(func(fd uintptr) { + _, _, errno = unix.Syscall6( + sys_GETSOCKOPT, + fd, + 2, /* #define SYSPROTO_CONTROL 2 */ + 2, /* #define UTUN_OPT_IFNAME 2 */ + uintptr(unsafe.Pointer(&ifName)), + uintptr(unsafe.Pointer(&ifNameSize)), 0) + }) + + if errno != 0 { + return "", fmt.Errorf("SYS_GETSOCKOPT: %v", errno) + } + + t.name = string(ifName.name[:ifNameSize-1]) + return t.name, nil +} + +func (t *tunDarwin) setMTU(n int) error { + // open datagram socket + fd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + + if err != nil { + return err + } + + defer unix.Close(fd) + + // do ioctl call + + var ifr [32]byte + copy(ifr[:], t.name) + *(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n) + _, _, errno := unix.Syscall( + sys_IOCTL, + uintptr(fd), + uintptr(unix.SIOCSIFMTU), + uintptr(unsafe.Pointer(&ifr[0])), + ) + + if errno != 0 { + return fmt.Errorf("failed to set MTU on %s", t.name) + } + + return nil +} + +func (t *tunDarwin) operateOnFd(fn func(fd uintptr)) { + sysconn, err := t.tunFile.SyscallConn() + // TODO: consume the errors + if err != nil { + t.errors <- fmt.Errorf("unable to find sysconn for tunfile: %s", err.Error()) + return + } + err = sysconn.Control(fn) + if err != nil { + t.errors <- fmt.Errorf("unable to control sysconn for tunfile: %s", err.Error()) + } +} + +func (t *tunDarwin) setTunAddress(addr net.IP) error { + var ifr [unix.IFNAMSIZ]byte + copy(ifr[:], t.name) + + // set IPv4 address + fd4, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + if err != nil { + return err + } + defer syscall.Close(fd4) + + // https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/sys/sockio.h#L107 + // https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/net/if.h#L570-L575 + // https://man.openbsd.org/netintro.4#SIOCAIFADDR + type aliasreq struct { + ifra_name [unix.IFNAMSIZ]byte + ifra_addr unix.RawSockaddrInet4 + ifra_dstaddr unix.RawSockaddrInet4 + ifra_mask unix.RawSockaddrInet4 + } + + var ip4 [4]byte + copy(ip4[:], addr.To4()) + ip4mask := [4]byte{255, 255, 0, 0} + ifra4 := aliasreq{ + ifra_name: ifr, + ifra_addr: unix.RawSockaddrInet4{ + Len: unix.SizeofSockaddrInet4, + Family: unix.AF_INET, + Addr: ip4, + }, + ifra_dstaddr: unix.RawSockaddrInet4{ + Len: unix.SizeofSockaddrInet4, + Family: unix.AF_INET, + Addr: ip4, + }, + ifra_mask: unix.RawSockaddrInet4{ + Len: unix.SizeofSockaddrInet4, + Family: unix.AF_INET, + Addr: ip4mask, + }, + } + + if _, _, errno := unix.Syscall( + sys_IOCTL, + uintptr(fd4), + uintptr(unix.SIOCAIFADDR), + uintptr(unsafe.Pointer(&ifra4)), + ); errno != 0 { + return fmt.Errorf("failed to set ip address on %s: %v", t.name, errno) + } + + return nil +} + +func (t *tunDarwin) attachLinkLocal() error { + var ifr [unix.IFNAMSIZ]byte + copy(ifr[:], t.name) + + // attach link-local address + fd6, err := unix.Socket( + unix.AF_INET6, + unix.SOCK_DGRAM, + 0, + ) + if err != nil { + return err + } + defer syscall.Close(fd6) + // SIOCAIFADDR_IN6 + // https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/in6_var.h#L114-L119 + // https://opensource.apple.com/source/network_cmds/network_cmds-543.260.3/ + type in6_addrlifetime struct { + //ia6t_expire uint64 + //ia6t_preferred uint64 + //ia6t_vltime uint32 + //ia6t_pltime uint32 + } + // https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/in6_var.h#L336-L343 + // https://github.com/apple/darwin-xnu/blob/a449c6a3b8014d9406c2ddbdc81795da24aa7443/bsd/netinet6/in6.h#L174-L181 + type in6_aliasreq struct { + ifra_name [unix.IFNAMSIZ]byte + ifra_addr unix.RawSockaddrInet6 + ifra_dstaddr unix.RawSockaddrInet6 + ifra_prefixmask unix.RawSockaddrInet6 + ifra_flags int32 + ifra_lifetime in6_addrlifetime + } + // Attach link-local address + ifra6 := in6_aliasreq{ + ifra_name: ifr, + } + if _, _, errno := unix.Syscall( + sys_IOCTL, + uintptr(fd6), + uintptr(_SIOCPROTOATTACH_IN6), + uintptr(unsafe.Pointer(&ifra6)), + ); errno != 0 { + return fmt.Errorf("failed to attach link-local address on %s: SIOCPROTOATTACH_IN6 %v", t.name, errno) + } + + if _, _, errno := unix.Syscall( + sys_IOCTL, + uintptr(fd6), + uintptr(_SIOCLL_START), + uintptr(unsafe.Pointer(&ifra6)), + ); errno != 0 { + return fmt.Errorf("failed to set ipv6 address on %s: SIOCLL_START %v", t.name, errno) + } + + return nil +} + +// GetAutoDetectInterface get ethernet interface +func GetAutoDetectInterface() (string, error) { + cmd := exec.Command("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return "", err + } + if out.Len() == 0 { + return "", errors.New("interface not found by default route") + } + return out.String(), nil +} diff --git a/listener/tun/dev/dev_linux.go b/listener/tun/dev/dev_linux.go new file mode 100644 index 00000000..0e985e81 --- /dev/null +++ b/listener/tun/dev/dev_linux.go @@ -0,0 +1,254 @@ +// +build linux android + +package dev + +import ( + "bytes" + "errors" + "fmt" + "net/url" + "os" + "os/exec" + "strconv" + "sync" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +const ( + cloneDevicePath = "/dev/net/tun" + ifReqSize = unix.IFNAMSIZ + 64 +) + +type tunLinux struct { + url string + name string + tunAddress string + autoRoute bool + tunFile *os.File + mtu int + + closed bool + stopOnce sync.Once +} + +// OpenTunDevice return a TunDevice according a URL +func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { + deviceURL, _ := url.Parse("dev://clash0") + mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32) + + t := &tunLinux{ + url: deviceURL.String(), + mtu: int(mtu), + tunAddress: tunAddress, + autoRoute: autoRoute, + } + switch deviceURL.Scheme { + case "dev": + var err error + var dev TunDevice + dev, err = t.openDeviceByName(deviceURL.Host) + if err != nil { + return nil, err + } + if autoRoute { + SetLinuxAutoRoute() + } + return dev, nil + case "fd": + fd, err := strconv.ParseInt(deviceURL.Host, 10, 32) + if err != nil { + return nil, err + } + var dev TunDevice + dev, err = t.openDeviceByFd(int(fd)) + if err != nil { + return nil, err + } + if autoRoute { + SetLinuxAutoRoute() + } + return dev, nil + } + return nil, fmt.Errorf("unsupported device type `%s`", deviceURL.Scheme) +} + +func (t *tunLinux) Name() string { + return t.name +} + +func (t *tunLinux) URL() string { + return t.url +} + +func (t *tunLinux) Write(buff []byte) (int, error) { + return t.tunFile.Write(buff) +} + +func (t *tunLinux) Read(buff []byte) (int, error) { + return t.tunFile.Read(buff) +} + +func (t *tunLinux) IsClose() bool { + return t.closed +} + +func (t *tunLinux) Close() error { + t.stopOnce.Do(func() { + if t.autoRoute { + RemoveLinuxAutoRoute() + } + t.closed = true + t.tunFile.Close() + }) + return nil +} + +func (t *tunLinux) MTU() (int, error) { + // Sometime, we can't read MTU by SIOCGIFMTU. Then we should return the preset MTU + if t.mtu > 0 { + return t.mtu, nil + } + mtu, err := t.getInterfaceMtu() + return int(mtu), err +} + +func (t *tunLinux) openDeviceByName(name string) (TunDevice, error) { + nfd, err := unix.Open(cloneDevicePath, os.O_RDWR, 0) + if err != nil { + return nil, err + } + + var ifr [ifReqSize]byte + var flags uint16 = unix.IFF_TUN | unix.IFF_NO_PI + nameBytes := []byte(name) + if len(nameBytes) >= unix.IFNAMSIZ { + return nil, errors.New("interface name too long") + } + copy(ifr[:], nameBytes) + *(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags + + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(nfd), + uintptr(unix.TUNSETIFF), + uintptr(unsafe.Pointer(&ifr[0])), + ) + if errno != 0 { + return nil, errno + } + err = unix.SetNonblock(nfd, true) + if err != nil { + return nil, err + } + + // Note that the above -- open,ioctl,nonblock -- must happen prior to handing it to netpoll as below this line. + + t.tunFile = os.NewFile(uintptr(nfd), cloneDevicePath) + t.name, err = t.getName() + if err != nil { + t.tunFile.Close() + return nil, err + } + + return t, nil +} + +func (t *tunLinux) openDeviceByFd(fd int) (TunDevice, error) { + var ifr struct { + name [16]byte + flags uint16 + _ [22]byte + } + + fd, err := syscall.Dup(fd) + if err != nil { + return nil, err + } + + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNGETIFF, uintptr(unsafe.Pointer(&ifr))) + if errno != 0 { + return nil, errno + } + + if ifr.flags&syscall.IFF_TUN == 0 || ifr.flags&syscall.IFF_NO_PI == 0 { + return nil, errors.New("only tun device and no pi mode supported") + } + + nullStr := ifr.name[:] + i := bytes.IndexByte(nullStr, 0) + if i != -1 { + nullStr = nullStr[:i] + } + t.name = string(nullStr) + t.tunFile = os.NewFile(uintptr(fd), "/dev/tun") + + return t, nil +} + +func (t *tunLinux) getInterfaceMtu() (uint32, error) { + fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) + if err != nil { + return 0, err + } + + defer syscall.Close(fd) + + var ifreq struct { + name [16]byte + mtu int32 + _ [20]byte + } + + copy(ifreq.name[:], t.name) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCGIFMTU, uintptr(unsafe.Pointer(&ifreq))) + if errno != 0 { + return 0, errno + } + + return uint32(ifreq.mtu), nil +} + +func (t *tunLinux) getName() (string, error) { + sysconn, err := t.tunFile.SyscallConn() + if err != nil { + return "", err + } + var ifr [ifReqSize]byte + var errno syscall.Errno + err = sysconn.Control(func(fd uintptr) { + _, _, errno = unix.Syscall( + unix.SYS_IOCTL, + fd, + uintptr(unix.TUNGETIFF), + uintptr(unsafe.Pointer(&ifr[0])), + ) + }) + if err != nil { + return "", errors.New("failed to get name of TUN device: " + err.Error()) + } + if errno != 0 { + return "", errors.New("failed to get name of TUN device: " + errno.Error()) + } + nullStr := ifr[:] + i := bytes.IndexByte(nullStr, 0) + if i != -1 { + nullStr = nullStr[:i] + } + t.name = string(nullStr) + return t.name, nil +} + +// GetAutoDetectInterface get ethernet interface +func GetAutoDetectInterface() (string, error) { + cmd := exec.Command("bash", "-c", "ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return "", err + } + return out.String(), nil +} diff --git a/listener/tun/dev/dev_unsupport.go b/listener/tun/dev/dev_unsupport.go new file mode 100644 index 00000000..3ad793ef --- /dev/null +++ b/listener/tun/dev/dev_unsupport.go @@ -0,0 +1,17 @@ +// +build !linux,!android,!darwin,!windows + +package dev + +import ( + "errors" + "runtime" +) + +func OpenTunDevice(tunAddress string, autoRute bool) (TunDevice, error) { + return nil, errors.New("Unsupported platform " + runtime.GOOS + "/" + runtime.GOARCH) +} + +// GetAutoDetectInterface get ethernet interface +func GetAutoDetectInterface() (string, error) { + return "", nil +} diff --git a/listener/tun/dev/dev_windows.go b/listener/tun/dev/dev_windows.go new file mode 100644 index 00000000..31056d9d --- /dev/null +++ b/listener/tun/dev/dev_windows.go @@ -0,0 +1,552 @@ +// +build windows + +package dev + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "sort" + "sync" + "sync/atomic" + "time" + _ "unsafe" + + "github.com/Dreamacro/clash/listener/tun/dev/winipcfg" + "github.com/Dreamacro/clash/listener/tun/dev/wintun" + "github.com/Dreamacro/clash/log" + "golang.org/x/sys/windows" +) + +const ( + rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond) + spinloopRateThreshold = 800000000 / 8 // 800mbps + spinloopDuration = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s + + messageTransportHeaderSize = 0 // size of data preceding content in transport message +) + +type rateJuggler struct { + current uint64 + nextByteCount uint64 + nextStartTime int64 + changing int32 +} + +type tunWindows struct { + wt *wintun.Adapter + handle windows.Handle + closed bool + closing sync.RWMutex + forcedMTU int + rate rateJuggler + session wintun.Session + readWait windows.Handle + stopOnce sync.Once + + url string + name string + tunAddress string + autoRoute bool +} + +var WintunPool, _ = wintun.MakePool("Clash") +var WintunStaticRequestedGUID *windows.GUID + +//go:linkname procyield runtime.procyield +func procyield(cycles uint32) + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +// OpenTunDevice return a TunDevice according a URL +func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) { + + requestedGUID, err := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}") + if err == nil { + WintunStaticRequestedGUID = &requestedGUID + log.Debugln("Generate GUID: %s", WintunStaticRequestedGUID.String()) + } else { + log.Warnln("Error parese GUID from string: %v", err) + } + + interfaceName := "Clash" + mtu := 9000 + + tun, err := CreateTUN(interfaceName, mtu, tunAddress, autoRoute) + if err != nil { + return nil, err + } + + return tun, nil +} + +// +// CreateTUN creates a Wintun interface with the given name. Should a Wintun +// interface with the same name exist, it is reused. +// +func CreateTUN(ifname string, mtu int, tunAddress string, autoRoute bool) (TunDevice, error) { + return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu, tunAddress, autoRoute) +} + +// +// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and +// a requested GUID. Should a Wintun interface with the same name exist, it is reused. +// +func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int, tunAddress string, autoRoute bool) (TunDevice, error) { + var err error + var wt *wintun.Adapter + + // Does an interface with this name already exist? + wt, err = WintunPool.OpenAdapter(ifname) + if err == nil { + // If so, we delete it, in case it has weird residual configuration. + _, err = wt.Delete(false) + if err != nil { + return nil, fmt.Errorf("Error deleting already existing interface: %w", err) + } + } + wt, rebootRequired, err := WintunPool.CreateAdapter(ifname, requestedGUID) + if err != nil { + return nil, fmt.Errorf("Error creating interface: %w", err) + } + if rebootRequired { + log.Infoln("Windows indicated a reboot is required.") + } + + forcedMTU := 1420 + if mtu > 0 { + forcedMTU = mtu + } + + tun := &tunWindows{ + wt: wt, + handle: windows.InvalidHandle, + forcedMTU: forcedMTU, + tunAddress: tunAddress, + autoRoute: autoRoute, + } + + // config tun ip + err = tun.configureInterface() + if err != nil { + tun.wt.Delete(false) + return nil, fmt.Errorf("Error configure interface: %w", err) + } + + realInterfaceName, err2 := wt.Name() + if err2 == nil { + ifname = realInterfaceName + tun.name = realInterfaceName + } + + tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB + if err != nil { + tun.wt.Delete(false) + return nil, fmt.Errorf("Error starting session: %w", err) + } + tun.readWait = tun.session.ReadWaitEvent() + return tun, nil +} + +func (tun *tunWindows) getName() (string, error) { + tun.closing.RLock() + defer tun.closing.RUnlock() + if tun.closed { + return "", os.ErrClosed + } + return tun.wt.Name() +} + +func (tun *tunWindows) IsClose() bool { + return tun.closed +} + +func (tun *tunWindows) Close() error { + tun.stopOnce.Do(func() { + //tun.closing.Lock() + //defer tun.closing.Unlock() + tun.closed = true + tun.session.End() + if tun.wt != nil { + forceCloseSessions := false + rebootRequired, err := tun.wt.Delete(forceCloseSessions) + if rebootRequired { + log.Infoln("Delete Wintun failure, Windows indicated a reboot is required.") + } else { + log.Infoln("Delete Wintun success.") + } + if err != nil { + log.Errorln("Close Wintun Sessions failure: %v", err) + } + } + }) + return nil +} + +func (tun *tunWindows) MTU() (int, error) { + return tun.forcedMTU, nil +} + +// TODO: This is a temporary hack. We really need to be monitoring the interface in real time and adapting to MTU changes. +func (tun *tunWindows) ForceMTU(mtu int) { + tun.forcedMTU = mtu +} + +func (tun *tunWindows) Read(buff []byte) (int, error) { + return tun.ReadO(buff, messageTransportHeaderSize) +} + +// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking. + +func (tun *tunWindows) ReadO(buff []byte, offset int) (int, error) { + tun.closing.RLock() + defer tun.closing.RUnlock() +retry: + if tun.closed { + return 0, os.ErrClosed + } + start := nanotime() + shouldSpin := atomic.LoadUint64(&tun.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&tun.rate.nextStartTime)) <= rateMeasurementGranularity*2 + for { + if tun.closed { + return 0, os.ErrClosed + } + packet, err := tun.session.ReceivePacket() + switch err { + case nil: + packetSize := len(packet) + copy(buff[offset:], packet) + tun.session.ReleaseReceivePacket(packet) + tun.rate.update(uint64(packetSize)) + return packetSize, nil + case windows.ERROR_NO_MORE_ITEMS: + if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration { + windows.WaitForSingleObject(tun.readWait, windows.INFINITE) + goto retry + } + procyield(1) + continue + case windows.ERROR_HANDLE_EOF: + return 0, os.ErrClosed + case windows.ERROR_INVALID_DATA: + return 0, errors.New("Send ring corrupt") + } + return 0, fmt.Errorf("Read failed: %w", err) + } +} + +func (tun *tunWindows) Flush() error { + return nil +} + +func (tun *tunWindows) Write(buff []byte) (int, error) { + return tun.WriteO(buff, messageTransportHeaderSize) +} + +func (tun *tunWindows) WriteO(buff []byte, offset int) (int, error) { + tun.closing.RLock() + defer tun.closing.RUnlock() + if tun.closed { + return 0, os.ErrClosed + } + + packetSize := len(buff) - offset + tun.rate.update(uint64(packetSize)) + + packet, err := tun.session.AllocateSendPacket(packetSize) + if err == nil { + copy(packet, buff[offset:]) + tun.session.SendPacket(packet) + return packetSize, nil + } + switch err { + case windows.ERROR_HANDLE_EOF: + return 0, os.ErrClosed + case windows.ERROR_BUFFER_OVERFLOW: + return 0, nil // Dropping when ring is full. + } + return 0, fmt.Errorf("Write failed: %w", err) +} + +// LUID returns Windows interface instance ID. +func (tun *tunWindows) LUID() uint64 { + tun.closing.RLock() + defer tun.closing.RUnlock() + if tun.closed { + return 0 + } + return tun.wt.LUID() +} + +// RunningVersion returns the running version of the Wintun driver. +func (tun *tunWindows) RunningVersion() (version uint32, err error) { + return wintun.RunningVersion() +} + +func (rate *rateJuggler) update(packetLen uint64) { + now := nanotime() + total := atomic.AddUint64(&rate.nextByteCount, packetLen) + period := uint64(now - atomic.LoadInt64(&rate.nextStartTime)) + if period >= rateMeasurementGranularity { + if !atomic.CompareAndSwapInt32(&rate.changing, 0, 1) { + return + } + atomic.StoreInt64(&rate.nextStartTime, now) + atomic.StoreUint64(&rate.current, total*uint64(time.Second/time.Nanosecond)/period) + atomic.StoreUint64(&rate.nextByteCount, 0) + atomic.StoreInt32(&rate.changing, 0) + } +} + +func (tun *tunWindows) Name() string { + return tun.name +} + +func (t *tunWindows) URL() string { + return fmt.Sprintf("dev://%s", t.Name()) +} + +func (tun *tunWindows) configureInterface() error { + luid := winipcfg.LUID(tun.LUID()) + + mtu, err := tun.MTU() + + if err != nil { + return errors.New("unable to get device mtu") + } + + family := winipcfg.AddressFamily(windows.AF_INET) + familyV6 := winipcfg.AddressFamily(windows.AF_INET6) + + tunAddress := winipcfg.ParseIPCidr("198.18.0.1/16") + + addresses := []net.IPNet{tunAddress.IPNet()} + + err = luid.FlushIPAddresses(familyV6) + if err != nil { + return err + } + err = luid.FlushDNS(family) + if err != nil { + return err + } + err = luid.FlushDNS(familyV6) + if err != nil { + return err + } + err = luid.FlushRoutes(familyV6) + if err != nil { + return err + } + + err = luid.SetIPAddressesForFamily(family, addresses) + + if err == windows.ERROR_OBJECT_ALREADY_EXISTS { + cleanupAddressesOnDisconnectedInterfaces(family, addresses) + err = luid.SetIPAddressesForFamily(family, addresses) + } + if err != nil { + return err + } + + foundDefault4 := false + foundDefault6 := false + + if tun.autoRoute { + allowedIPs := []*winipcfg.IPCidr{ + winipcfg.ParseIPCidr("1.0.0.0/8"), + winipcfg.ParseIPCidr("2.0.0.0/7"), + winipcfg.ParseIPCidr("4.0.0.0/6"), + winipcfg.ParseIPCidr("8.0.0.0/5"), + winipcfg.ParseIPCidr("16.0.0.0/4"), + winipcfg.ParseIPCidr("32.0.0.0/3"), + winipcfg.ParseIPCidr("64.0.0.0/2"), + winipcfg.ParseIPCidr("128.0.0.0/1"), + //winipcfg.ParseIPCidr("198.18.0.0/16"), + //winipcfg.ParseIPCidr("198.18.0.1/32"), + //winipcfg.ParseIPCidr("198.18.255.255/32"), + winipcfg.ParseIPCidr("224.0.0.0/4"), + winipcfg.ParseIPCidr("255.255.255.255/32"), + } + + estimatedRouteCount := len(allowedIPs) + routes := make([]winipcfg.RouteData, 0, estimatedRouteCount) + var haveV4Address, haveV6Address bool = true, false + + for _, allowedip := range allowedIPs { + allowedip.MaskSelf() + if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) { + continue + } + route := winipcfg.RouteData{ + Destination: allowedip.IPNet(), + Metric: 0, + } + if allowedip.Bits() == 32 { + if allowedip.Cidr == 0 { + foundDefault4 = true + } + route.NextHop = net.IPv4zero + } else if allowedip.Bits() == 128 { + if allowedip.Cidr == 0 { + foundDefault6 = true + } + route.NextHop = net.IPv6zero + } + routes = append(routes, route) + } + + deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes)) + sort.Slice(routes, func(i, j int) bool { + if routes[i].Metric != routes[j].Metric { + return routes[i].Metric < routes[j].Metric + } + if c := bytes.Compare(routes[i].NextHop, routes[j].NextHop); c != 0 { + return c < 0 + } + if c := bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP); c != 0 { + return c < 0 + } + if c := bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask); c != 0 { + return c < 0 + } + return false + }) + for i := 0; i < len(routes); i++ { + if i > 0 && routes[i].Metric == routes[i-1].Metric && + bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) && + bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) && + bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) { + continue + } + deduplicatedRoutes = append(deduplicatedRoutes, &routes[i]) + } + + err = luid.SetRoutesForFamily(family, deduplicatedRoutes) + if err != nil { + return err + } + } + + ipif, err := luid.IPInterface(family) + if err != nil { + return err + } + + ipif.NLMTU = uint32(mtu) + + if family == windows.AF_INET { + if foundDefault4 { + ipif.UseAutomaticMetric = false + ipif.Metric = 0 + } + } else if family == windows.AF_INET6 { + if foundDefault6 { + ipif.UseAutomaticMetric = false + ipif.Metric = 0 + } + ipif.DadTransmits = 0 + ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled + } + + err = ipif.Set() + if err != nil { + return err + } + + ipif6, err := luid.IPInterface(familyV6) + if err != nil { + return err + } + err = ipif6.Set() + if err != nil { + return err + } + + return luid.SetDNS(family, []net.IP{net.ParseIP("198.18.0.2")}, nil) +} + +func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { + if len(addresses) == 0 { + return + } + includedInAddresses := func(a net.IPNet) bool { + // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! + for _, addr := range addresses { + ip := addr.IP + if ip4 := ip.To4(); ip4 != nil { + ip = ip4 + } + mA, _ := addr.Mask.Size() + mB, _ := a.Mask.Size() + if bytes.Equal(ip, a.IP) && mA == mB { + return true + } + } + return false + } + interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) + if err != nil { + return + } + for _, iface := range interfaces { + if iface.OperStatus == winipcfg.IfOperStatusUp { + continue + } + for address := iface.FirstUnicastAddress; address != nil; address = address.Next { + ip := address.Address.IP() + ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} + if includedInAddresses(ipnet) { + log.Infoln("[Wintun] Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName()) + iface.LUID.DeleteIPAddress(ipnet) + } + } + } +} + +// GetAutoDetectInterface get ethernet interface +func GetAutoDetectInterface() (string, error) { + ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET)) + if err == nil { + return ifname, err + } + + return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6)) +} + +func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) { + interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways) + if err != nil { + return "", fmt.Errorf("find ethernet interface failure. %w", err) + } + for _, iface := range interfaces { + if iface.OperStatus != winipcfg.IfOperStatusUp { + continue + } + + ifname := iface.FriendlyName() + if ifname == "Clash" { + continue + } + + for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next { + nextHop := gatewayAddress.Address.IP() + + var ipnet net.IPNet + if family == windows.AF_INET { + ipnet = net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)} + } else { + ipnet = net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)} + } + + if _, err = iface.LUID.Route(ipnet, nextHop); err == nil { + return ifname, nil + } + } + } + + return "", errors.New("ethernet interface not found") +} diff --git a/listener/tun/dev/winipcfg/config.go b/listener/tun/dev/winipcfg/config.go new file mode 100644 index 00000000..9e4b0a4e --- /dev/null +++ b/listener/tun/dev/winipcfg/config.go @@ -0,0 +1,56 @@ +// +build windows + +package winipcfg + +import ( + "fmt" + "net" + "strconv" + "strings" +) + +type IPCidr struct { + IP net.IP + Cidr uint8 +} + +func (r *IPCidr) String() string { + return fmt.Sprintf("%s/%d", r.IP.String(), r.Cidr) +} + +func (r *IPCidr) Bits() uint8 { + if r.IP.To4() != nil { + return 32 + } + return 128 +} + +func (r *IPCidr) IPNet() net.IPNet { + return net.IPNet{ + IP: r.IP, + Mask: net.CIDRMask(int(r.Cidr), int(r.Bits())), + } +} + +func (r *IPCidr) MaskSelf() { + bits := int(r.Bits()) + mask := net.CIDRMask(int(r.Cidr), bits) + for i := 0; i < bits/8; i++ { + r.IP[i] &= mask[i] + } +} + +func ParseIPCidr(ipcidr string) *IPCidr { + s := strings.Split(ipcidr, "/") + if len(s) != 2 { + return nil + } + cidr, err := strconv.Atoi(s[1]) + if err != nil { + return nil + } + return &IPCidr{ + IP: net.ParseIP(s[0]), + Cidr: uint8(cidr), + } +} diff --git a/listener/tun/dev/winipcfg/interface_change_handler.go b/listener/tun/dev/winipcfg/interface_change_handler.go new file mode 100644 index 00000000..10e53692 --- /dev/null +++ b/listener/tun/dev/winipcfg/interface_change_handler.go @@ -0,0 +1,85 @@ +// +build windows + +package winipcfg + +import ( + "sync" + + "golang.org/x/sys/windows" +) + +// InterfaceChangeCallback structure allows interface change callback handling. +type InterfaceChangeCallback struct { + cb func(notificationType MibNotificationType, iface *MibIPInterfaceRow) + wait sync.WaitGroup +} + +var ( + interfaceChangeAddRemoveMutex = sync.Mutex{} + interfaceChangeMutex = sync.Mutex{} + interfaceChangeCallbacks = make(map[*InterfaceChangeCallback]bool) + interfaceChangeHandle = windows.Handle(0) +) + +// RegisterInterfaceChangeCallback registers a new InterfaceChangeCallback. If this particular callback is already +// registered, the function will silently return. Returned InterfaceChangeCallback.Unregister method should be used +// to unregister. +func RegisterInterfaceChangeCallback(callback func(notificationType MibNotificationType, iface *MibIPInterfaceRow)) (*InterfaceChangeCallback, error) { + s := &InterfaceChangeCallback{cb: callback} + + interfaceChangeAddRemoveMutex.Lock() + defer interfaceChangeAddRemoveMutex.Unlock() + + interfaceChangeMutex.Lock() + defer interfaceChangeMutex.Unlock() + + interfaceChangeCallbacks[s] = true + + if interfaceChangeHandle == 0 { + err := notifyIPInterfaceChange(windows.AF_UNSPEC, windows.NewCallback(interfaceChanged), 0, false, &interfaceChangeHandle) + if err != nil { + delete(interfaceChangeCallbacks, s) + interfaceChangeHandle = 0 + return nil, err + } + } + + return s, nil +} + +// Unregister unregisters the callback. +func (callback *InterfaceChangeCallback) Unregister() error { + interfaceChangeAddRemoveMutex.Lock() + defer interfaceChangeAddRemoveMutex.Unlock() + + interfaceChangeMutex.Lock() + delete(interfaceChangeCallbacks, callback) + removeIt := len(interfaceChangeCallbacks) == 0 && interfaceChangeHandle != 0 + interfaceChangeMutex.Unlock() + + callback.wait.Wait() + + if removeIt { + err := cancelMibChangeNotify2(interfaceChangeHandle) + if err != nil { + return err + } + interfaceChangeHandle = 0 + } + + return nil +} + +func interfaceChanged(callerContext uintptr, row *MibIPInterfaceRow, notificationType MibNotificationType) uintptr { + rowCopy := *row + interfaceChangeMutex.Lock() + for cb := range interfaceChangeCallbacks { + cb.wait.Add(1) + go func(cb *InterfaceChangeCallback) { + cb.cb(notificationType, &rowCopy) + cb.wait.Done() + }(cb) + } + interfaceChangeMutex.Unlock() + return 0 +} diff --git a/listener/tun/dev/winipcfg/luid.go b/listener/tun/dev/winipcfg/luid.go new file mode 100644 index 00000000..d76d31d0 --- /dev/null +++ b/listener/tun/dev/winipcfg/luid.go @@ -0,0 +1,383 @@ +// +build windows + +package winipcfg + +import ( + "errors" + "net" + "strings" + + "golang.org/x/sys/windows" +) + +// LUID represents a network interface. +type LUID uint64 + +// IPInterface method retrieves IP information for the specified interface on the local computer. +func (luid LUID) IPInterface(family AddressFamily) (*MibIPInterfaceRow, error) { + row := &MibIPInterfaceRow{} + row.Init() + row.InterfaceLUID = luid + row.Family = family + err := row.get() + if err != nil { + return nil, err + } + return row, nil +} + +// Interface method retrieves information for the specified adapter on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getifentry2 +func (luid LUID) Interface() (*MibIfRow2, error) { + row := &MibIfRow2{} + row.InterfaceLUID = luid + err := row.get() + if err != nil { + return nil, err + } + return row, nil +} + +// GUID method converts a locally unique identifier (LUID) for a network interface to a globally unique identifier (GUID) for the interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceluidtoguid +func (luid LUID) GUID() (*windows.GUID, error) { + guid := &windows.GUID{} + err := convertInterfaceLUIDToGUID(&luid, guid) + if err != nil { + return nil, err + } + return guid, nil +} + +// LUIDFromGUID function converts a globally unique identifier (GUID) for a network interface to the locally unique identifier (LUID) for the interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceguidtoluid +func LUIDFromGUID(guid *windows.GUID) (LUID, error) { + var luid LUID + err := convertInterfaceGUIDToLUID(guid, &luid) + if err != nil { + return 0, err + } + return luid, nil +} + +// LUIDFromIndex function converts a local index for a network interface to the locally unique identifier (LUID) for the interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceindextoluid +func LUIDFromIndex(index uint32) (LUID, error) { + var luid LUID + err := convertInterfaceIndexToLUID(index, &luid) + if err != nil { + return 0, err + } + return luid, nil +} + +// IPAddress method returns MibUnicastIPAddressRow struct that matches to provided 'ip' argument. Corresponds to GetUnicastIpAddressEntry +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddressentry) +func (luid LUID) IPAddress(ip net.IP) (*MibUnicastIPAddressRow, error) { + row := &MibUnicastIPAddressRow{InterfaceLUID: luid} + + err := row.Address.SetIP(ip, 0) + if err != nil { + return nil, err + } + + err = row.get() + if err != nil { + return nil, err + } + + return row, nil +} + +// AddIPAddress method adds new unicast IP address to the interface. Corresponds to CreateUnicastIpAddressEntry function +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). +func (luid LUID) AddIPAddress(address net.IPNet) error { + row := &MibUnicastIPAddressRow{} + row.Init() + row.InterfaceLUID = luid + err := row.Address.SetIP(address.IP, 0) + if err != nil { + return err + } + ones, _ := address.Mask.Size() + row.OnLinkPrefixLength = uint8(ones) + return row.Create() +} + +// AddIPAddresses method adds multiple new unicast IP addresses to the interface. Corresponds to CreateUnicastIpAddressEntry function +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). +func (luid LUID) AddIPAddresses(addresses []net.IPNet) error { + for i := range addresses { + err := luid.AddIPAddress(addresses[i]) + if err != nil { + return err + } + } + return nil +} + +// SetIPAddresses method sets new unicast IP addresses to the interface. +func (luid LUID) SetIPAddresses(addresses []net.IPNet) error { + err := luid.FlushIPAddresses(windows.AF_UNSPEC) + if err != nil { + return err + } + return luid.AddIPAddresses(addresses) +} + +// SetIPAddressesForFamily method sets new unicast IP addresses for a specific family to the interface. +func (luid LUID) SetIPAddressesForFamily(family AddressFamily, addresses []net.IPNet) error { + err := luid.FlushIPAddresses(family) + if err != nil { + return err + } + for i := range addresses { + asV4 := addresses[i].IP.To4() + if asV4 == nil && family == windows.AF_INET { + continue + } else if asV4 != nil && family == windows.AF_INET6 { + continue + } + err := luid.AddIPAddress(addresses[i]) + if err != nil { + return err + } + } + return nil +} + +// DeleteIPAddress method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry). +func (luid LUID) DeleteIPAddress(address net.IPNet) error { + row := &MibUnicastIPAddressRow{} + row.Init() + row.InterfaceLUID = luid + err := row.Address.SetIP(address.IP, 0) + if err != nil { + return err + } + // Note: OnLinkPrefixLength member is ignored by DeleteUnicastIpAddressEntry(). + ones, _ := address.Mask.Size() + row.OnLinkPrefixLength = uint8(ones) + return row.Delete() +} + +// FlushIPAddresses method deletes all interface's unicast IP addresses. +func (luid LUID) FlushIPAddresses(family AddressFamily) error { + var tab *mibUnicastIPAddressTable + err := getUnicastIPAddressTable(family, &tab) + if err != nil { + return err + } + t := tab.get() + for i := range t { + if t[i].InterfaceLUID == luid { + t[i].Delete() + } + } + tab.free() + return nil +} + +// Route method returns route determined with the input arguments. Corresponds to GetIpForwardEntry2 function +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardentry2). +// NOTE: If the corresponding route isn't found, the method will return error. +func (luid LUID) Route(destination net.IPNet, nextHop net.IP) (*MibIPforwardRow2, error) { + row := &MibIPforwardRow2{} + row.Init() + row.InterfaceLUID = luid + err := row.DestinationPrefix.SetIPNet(destination) + if err != nil { + return nil, err + } + err = row.NextHop.SetIP(nextHop, 0) + if err != nil { + return nil, err + } + + err = row.get() + if err != nil { + return nil, err + } + return row, nil +} + +// AddRoute method adds a route to the interface. Corresponds to CreateIpForwardEntry2 function, with added splitDefault feature. +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createipforwardentry2) +func (luid LUID) AddRoute(destination net.IPNet, nextHop net.IP, metric uint32) error { + row := &MibIPforwardRow2{} + row.Init() + row.InterfaceLUID = luid + err := row.DestinationPrefix.SetIPNet(destination) + if err != nil { + return err + } + err = row.NextHop.SetIP(nextHop, 0) + if err != nil { + return err + } + row.Metric = metric + return row.Create() +} + +// AddRoutes method adds multiple routes to the interface. +func (luid LUID) AddRoutes(routesData []*RouteData) error { + for _, rd := range routesData { + err := luid.AddRoute(rd.Destination, rd.NextHop, rd.Metric) + if err != nil { + return err + } + } + return nil +} + +// SetRoutes method sets (flush than add) multiple routes to the interface. +func (luid LUID) SetRoutes(routesData []*RouteData) error { + err := luid.FlushRoutes(windows.AF_UNSPEC) + if err != nil { + return err + } + return luid.AddRoutes(routesData) +} + +// SetRoutesForFamily method sets (flush than add) multiple routes for a specific family to the interface. +func (luid LUID) SetRoutesForFamily(family AddressFamily, routesData []*RouteData) error { + err := luid.FlushRoutes(family) + if err != nil { + return err + } + for _, rd := range routesData { + asV4 := rd.Destination.IP.To4() + if asV4 == nil && family == windows.AF_INET { + continue + } else if asV4 != nil && family == windows.AF_INET6 { + continue + } + err := luid.AddRoute(rd.Destination, rd.NextHop, rd.Metric) + if err != nil { + return err + } + } + return nil +} + +// DeleteRoute method deletes a route that matches the criteria. Corresponds to DeleteIpForwardEntry2 function +// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteipforwardentry2). +func (luid LUID) DeleteRoute(destination net.IPNet, nextHop net.IP) error { + row := &MibIPforwardRow2{} + row.Init() + row.InterfaceLUID = luid + err := row.DestinationPrefix.SetIPNet(destination) + if err != nil { + return err + } + err = row.NextHop.SetIP(nextHop, 0) + if err != nil { + return err + } + err = row.get() + if err != nil { + return err + } + return row.Delete() +} + +// FlushRoutes method deletes all interface's routes. +// It continues on failures, and returns the last error afterwards. +func (luid LUID) FlushRoutes(family AddressFamily) error { + var tab *mibIPforwardTable2 + err := getIPForwardTable2(family, &tab) + if err != nil { + return err + } + t := tab.get() + for i := range t { + if t[i].InterfaceLUID == luid { + err2 := t[i].Delete() + if err2 != nil { + err = err2 + } + } + } + tab.free() + return err +} + +// DNS method returns all DNS server addresses associated with the adapter. +func (luid LUID) DNS() ([]net.IP, error) { + addresses, err := GetAdaptersAddresses(windows.AF_UNSPEC, GAAFlagDefault) + if err != nil { + return nil, err + } + r := make([]net.IP, 0, len(addresses)) + for _, addr := range addresses { + if addr.LUID == luid { + for dns := addr.FirstDNSServerAddress; dns != nil; dns = dns.Next { + if ip := dns.Address.IP(); ip != nil { + r = append(r, ip) + } else { + return nil, windows.ERROR_INVALID_PARAMETER + } + } + } + } + return r, nil +} + +// SetDNS method clears previous and associates new DNS servers and search domains with the adapter for a specific family. +func (luid LUID) SetDNS(family AddressFamily, servers []net.IP, domains []string) error { + if family != windows.AF_INET && family != windows.AF_INET6 { + return windows.ERROR_PROTOCOL_UNREACHABLE + } + + var filteredServers []string + for _, server := range servers { + if v4 := server.To4(); v4 != nil && family == windows.AF_INET { + filteredServers = append(filteredServers, v4.String()) + } else if v6 := server.To16(); v4 == nil && v6 != nil && family == windows.AF_INET6 { + filteredServers = append(filteredServers, v6.String()) + } + } + servers16, err := windows.UTF16PtrFromString(strings.Join(filteredServers, ",")) + if err != nil { + return err + } + domains16, err := windows.UTF16PtrFromString(strings.Join(domains, ",")) + if err != nil { + return err + } + guid, err := luid.GUID() + if err != nil { + return err + } + var maybeV6 uint64 + if family == windows.AF_INET6 { + maybeV6 = disFlagsIPv6 + } + // For >= Windows 10 1809 + err = setInterfaceDnsSettings(*guid, &dnsInterfaceSettings{ + Version: disVersion1, + Flags: disFlagsNameServer | disFlagsSearchList | maybeV6, + NameServer: servers16, + SearchList: domains16, + }) + if err == nil || !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) { + return err + } + + // For < Windows 10 1809 + err = luid.fallbackSetDNSForFamily(family, servers) + if err != nil { + return err + } + if len(domains) > 0 { + return luid.fallbackSetDNSDomain(domains[0]) + } else { + return luid.fallbackSetDNSDomain("") + } +} + +// FlushDNS method clears all DNS servers associated with the adapter. +func (luid LUID) FlushDNS(family AddressFamily) error { + return luid.SetDNS(family, nil, nil) +} diff --git a/listener/tun/dev/winipcfg/mksyscall.go b/listener/tun/dev/winipcfg/mksyscall.go new file mode 100644 index 00000000..a1f5db71 --- /dev/null +++ b/listener/tun/dev/winipcfg/mksyscall.go @@ -0,0 +1,3 @@ +package winipcfg + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinipcfg_windows.go winipcfg.go diff --git a/listener/tun/dev/winipcfg/netsh.go b/listener/tun/dev/winipcfg/netsh.go new file mode 100644 index 00000000..13fa37c7 --- /dev/null +++ b/listener/tun/dev/winipcfg/netsh.go @@ -0,0 +1,105 @@ +// +build windows + +package winipcfg + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +func runNetsh(cmds []string) error { + system32, err := windows.GetSystemDirectory() + if err != nil { + return err + } + cmd := exec.Command(filepath.Join(system32, "netsh.exe")) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + + stdin, err := cmd.StdinPipe() + if err != nil { + return fmt.Errorf("runNetsh stdin pipe - %w", err) + } + go func() { + defer stdin.Close() + io.WriteString(stdin, strings.Join(append(cmds, "exit\r\n"), "\r\n")) + }() + output, err := cmd.CombinedOutput() + // Horrible kludges, sorry. + cleaned := bytes.ReplaceAll(output, []byte{'\r', '\n'}, []byte{'\n'}) + cleaned = bytes.ReplaceAll(cleaned, []byte("netsh>"), []byte{}) + cleaned = bytes.ReplaceAll(cleaned, []byte("There are no Domain Name Servers (DNS) configured on this computer."), []byte{}) + cleaned = bytes.TrimSpace(cleaned) + if len(cleaned) != 0 && err == nil { + return fmt.Errorf("netsh: %#q", string(cleaned)) + } else if err != nil { + return fmt.Errorf("netsh: %v: %#q", err, string(cleaned)) + } + return nil +} + +const ( + netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no register=both" + netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no register=both" + netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no" + netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no" +) + +func (luid LUID) fallbackSetDNSForFamily(family AddressFamily, dnses []net.IP) error { + var templateFlush string + if family == windows.AF_INET { + templateFlush = netshCmdTemplateFlush4 + } else if family == windows.AF_INET6 { + templateFlush = netshCmdTemplateFlush6 + } + + cmds := make([]string, 0, 1+len(dnses)) + ipif, err := luid.IPInterface(family) + if err != nil { + return err + } + cmds = append(cmds, fmt.Sprintf(templateFlush, ipif.InterfaceIndex)) + for i := 0; i < len(dnses); i++ { + if v4 := dnses[i].To4(); v4 != nil && family == windows.AF_INET { + cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif.InterfaceIndex, v4.String())) + } else if v6 := dnses[i].To16(); v4 == nil && v6 != nil && family == windows.AF_INET6 { + cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif.InterfaceIndex, v6.String())) + } + } + return runNetsh(cmds) +} + +func (luid LUID) fallbackSetDNSDomain(domain string) error { + guid, err := luid.GUID() + if err != nil { + return fmt.Errorf("Error converting luid to guid: %w", err) + } + key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%v", guid), registry.QUERY_VALUE) + if err != nil { + return fmt.Errorf("Error opening adapter-specific TCP/IP network registry key: %w", err) + } + paths, _, err := key.GetStringsValue("IpConfig") + key.Close() + if err != nil { + return fmt.Errorf("Error reading IpConfig registry key: %w", err) + } + if len(paths) == 0 { + return errors.New("No TCP/IP interfaces found on adapter") + } + key, err = registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\%s", paths[0]), registry.SET_VALUE) + if err != nil { + return fmt.Errorf("Unable to open TCP/IP network registry key: %w", err) + } + err = key.SetStringValue("Domain", domain) + key.Close() + return err +} diff --git a/listener/tun/dev/winipcfg/route_change_handler.go b/listener/tun/dev/winipcfg/route_change_handler.go new file mode 100644 index 00000000..efb10247 --- /dev/null +++ b/listener/tun/dev/winipcfg/route_change_handler.go @@ -0,0 +1,85 @@ +// +build windows + +package winipcfg + +import ( + "sync" + + "golang.org/x/sys/windows" +) + +// RouteChangeCallback structure allows route change callback handling. +type RouteChangeCallback struct { + cb func(notificationType MibNotificationType, route *MibIPforwardRow2) + wait sync.WaitGroup +} + +var ( + routeChangeAddRemoveMutex = sync.Mutex{} + routeChangeMutex = sync.Mutex{} + routeChangeCallbacks = make(map[*RouteChangeCallback]bool) + routeChangeHandle = windows.Handle(0) +) + +// RegisterRouteChangeCallback registers a new RouteChangeCallback. If this particular callback is already +// registered, the function will silently return. Returned RouteChangeCallback.Unregister method should be used +// to unregister. +func RegisterRouteChangeCallback(callback func(notificationType MibNotificationType, route *MibIPforwardRow2)) (*RouteChangeCallback, error) { + s := &RouteChangeCallback{cb: callback} + + routeChangeAddRemoveMutex.Lock() + defer routeChangeAddRemoveMutex.Unlock() + + routeChangeMutex.Lock() + defer routeChangeMutex.Unlock() + + routeChangeCallbacks[s] = true + + if routeChangeHandle == 0 { + err := notifyRouteChange2(windows.AF_UNSPEC, windows.NewCallback(routeChanged), 0, false, &routeChangeHandle) + if err != nil { + delete(routeChangeCallbacks, s) + routeChangeHandle = 0 + return nil, err + } + } + + return s, nil +} + +// Unregister unregisters the callback. +func (callback *RouteChangeCallback) Unregister() error { + routeChangeAddRemoveMutex.Lock() + defer routeChangeAddRemoveMutex.Unlock() + + routeChangeMutex.Lock() + delete(routeChangeCallbacks, callback) + removeIt := len(routeChangeCallbacks) == 0 && routeChangeHandle != 0 + routeChangeMutex.Unlock() + + callback.wait.Wait() + + if removeIt { + err := cancelMibChangeNotify2(routeChangeHandle) + if err != nil { + return err + } + routeChangeHandle = 0 + } + + return nil +} + +func routeChanged(callerContext uintptr, row *MibIPforwardRow2, notificationType MibNotificationType) uintptr { + rowCopy := *row + routeChangeMutex.Lock() + for cb := range routeChangeCallbacks { + cb.wait.Add(1) + go func(cb *RouteChangeCallback) { + cb.cb(notificationType, &rowCopy) + cb.wait.Done() + }(cb) + } + routeChangeMutex.Unlock() + return 0 +} diff --git a/listener/tun/dev/winipcfg/types.go b/listener/tun/dev/winipcfg/types.go new file mode 100644 index 00000000..e71eda43 --- /dev/null +++ b/listener/tun/dev/winipcfg/types.go @@ -0,0 +1,993 @@ +// +build windows + +package winipcfg + +import ( + "net" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + anySize = 1 + maxDNSSuffixStringLength = 256 + maxDHCPv6DUIDLength = 130 + ifMaxStringSize = 256 + ifMaxPhysAddressLength = 32 +) + +// AddressFamily enumeration specifies protocol family and is one of the windows.AF_* constants. +type AddressFamily uint16 + +// IPAAFlags enumeration describes adapter addresses flags +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_addresses_lh +type IPAAFlags uint32 + +const ( + IPAAFlagDdnsEnabled IPAAFlags = 1 << iota + IPAAFlagRegisterAdapterSuffix + IPAAFlagDhcpv4Enabled + IPAAFlagReceiveOnly + IPAAFlagNoMulticast + IPAAFlagIpv6OtherStatefulConfig + IPAAFlagNetbiosOverTcpipEnabled + IPAAFlagIpv4Enabled + IPAAFlagIpv6Enabled + IPAAFlagIpv6ManagedAddressConfigurationSupported +) + +// IfOperStatus enumeration specifies the operational status of an interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-if_oper_status +type IfOperStatus uint32 + +const ( + IfOperStatusUp IfOperStatus = iota + 1 + IfOperStatusDown + IfOperStatusTesting + IfOperStatusUnknown + IfOperStatusDormant + IfOperStatusNotPresent + IfOperStatusLowerLayerDown +) + +// IfType enumeration specifies interface type. +type IfType uint32 + +const ( + IfTypeOther IfType = 1 // None of the below + IfTypeRegular1822 = 2 + IfTypeHdh1822 = 3 + IfTypeDdnX25 = 4 + IfTypeRfc877X25 = 5 + IfTypeEthernetCSMACD = 6 + IfTypeISO88023CSMACD = 7 + IfTypeISO88024Tokenbus = 8 + IfTypeISO88025Tokenring = 9 + IfTypeISO88026Man = 10 + IfTypeStarlan = 11 + IfTypeProteon10Mbit = 12 + IfTypeProteon80Mbit = 13 + IfTypeHyperchannel = 14 + IfTypeFddi = 15 + IfTypeLapB = 16 + IfTypeSdlc = 17 + IfTypeDs1 = 18 // DS1-MIB + IfTypeE1 = 19 // Obsolete; see DS1-MIB + IfTypeBasicISDN = 20 + IfTypePrimaryISDN = 21 + IfTypePropPoint2PointSerial = 22 // proprietary serial + IfTypePPP = 23 + IfTypeSoftwareLoopback = 24 + IfTypeEon = 25 // CLNP over IP + IfTypeEthernet3Mbit = 26 + IfTypeNsip = 27 // XNS over IP + IfTypeSlip = 28 // Generic Slip + IfTypeUltra = 29 // ULTRA Technologies + IfTypeDs3 = 30 // DS3-MIB + IfTypeSip = 31 // SMDS, coffee + IfTypeFramerelay = 32 // DTE only + IfTypeRs232 = 33 + IfTypePara = 34 // Parallel port + IfTypeArcnet = 35 + IfTypeArcnetPlus = 36 + IfTypeAtm = 37 // ATM cells + IfTypeMioX25 = 38 + IfTypeSonet = 39 // SONET or SDH + IfTypeX25Ple = 40 + IfTypeIso88022LLC = 41 + IfTypeLocaltalk = 42 + IfTypeSmdsDxi = 43 + IfTypeFramerelayService = 44 // FRNETSERV-MIB + IfTypeV35 = 45 + IfTypeHssi = 46 + IfTypeHippi = 47 + IfTypeModem = 48 // Generic Modem + IfTypeAal5 = 49 // AAL5 over ATM + IfTypeSonetPath = 50 + IfTypeSonetVt = 51 + IfTypeSmdsIcip = 52 // SMDS InterCarrier Interface + IfTypePropVirtual = 53 // Proprietary virtual/internal + IfTypePropMultiplexor = 54 // Proprietary multiplexing + IfTypeIEEE80212 = 55 // 100BaseVG + IfTypeFibrechannel = 56 + IfTypeHippiinterface = 57 + IfTypeFramerelayInterconnect = 58 // Obsolete, use 32 or 44 + IfTypeAflane8023 = 59 // ATM Emulated LAN for 802.3 + IfTypeAflane8025 = 60 // ATM Emulated LAN for 802.5 + IfTypeCctemul = 61 // ATM Emulated circuit + IfTypeFastether = 62 // Fast Ethernet (100BaseT) + IfTypeISDN = 63 // ISDN and X.25 + IfTypeV11 = 64 // CCITT V.11/X.21 + IfTypeV36 = 65 // CCITT V.36 + IfTypeG703_64k = 66 // CCITT G703 at 64Kbps + IfTypeG703_2mb = 67 // Obsolete; see DS1-MIB + IfTypeQllc = 68 // SNA QLLC + IfTypeFastetherFX = 69 // Fast Ethernet (100BaseFX) + IfTypeChannel = 70 + IfTypeIEEE80211 = 71 // Radio spread spectrum + IfTypeIBM370parchan = 72 // IBM System 360/370 OEMI Channel + IfTypeEscon = 73 // IBM Enterprise Systems Connection + IfTypeDlsw = 74 // Data Link Switching + IfTypeISDNS = 75 // ISDN S/T interface + IfTypeISDNU = 76 // ISDN U interface + IfTypeLapD = 77 // Link Access Protocol D + IfTypeIpswitch = 78 // IP Switching Objects + IfTypeRsrb = 79 // Remote Source Route Bridging + IfTypeAtmLogical = 80 // ATM Logical Port + IfTypeDs0 = 81 // Digital Signal Level 0 + IfTypeDs0Bundle = 82 // Group of ds0s on the same ds1 + IfTypeBsc = 83 // Bisynchronous Protocol + IfTypeAsync = 84 // Asynchronous Protocol + IfTypeCnr = 85 // Combat Net Radio + IfTypeIso88025rDtr = 86 // ISO 802.5r DTR + IfTypeEplrs = 87 // Ext Pos Loc Report Sys + IfTypeArap = 88 // Appletalk Remote Access Protocol + IfTypePropCnls = 89 // Proprietary Connectionless Proto + IfTypeHostpad = 90 // CCITT-ITU X.29 PAD Protocol + IfTypeTermpad = 91 // CCITT-ITU X.3 PAD Facility + IfTypeFramerelayMpi = 92 // Multiproto Interconnect over FR + IfTypeX213 = 93 // CCITT-ITU X213 + IfTypeAdsl = 94 // Asymmetric Digital Subscrbr Loop + IfTypeRadsl = 95 // Rate-Adapt Digital Subscrbr Loop + IfTypeSdsl = 96 // Symmetric Digital Subscriber Loop + IfTypeVdsl = 97 // Very H-Speed Digital Subscrb Loop + IfTypeIso88025Crfprint = 98 // ISO 802.5 CRFP + IfTypeMyrinet = 99 // Myricom Myrinet + IfTypeVoiceEm = 100 // Voice recEive and transMit + IfTypeVoiceFxo = 101 // Voice Foreign Exchange Office + IfTypeVoiceFxs = 102 // Voice Foreign Exchange Station + IfTypeVoiceEncap = 103 // Voice encapsulation + IfTypeVoiceOverip = 104 // Voice over IP encapsulation + IfTypeAtmDxi = 105 // ATM DXI + IfTypeAtmFuni = 106 // ATM FUNI + IfTypeAtmIma = 107 // ATM IMA + IfTypePPPmultilinkbundle = 108 // PPP Multilink Bundle + IfTypeIpoverCdlc = 109 // IBM ipOverCdlc + IfTypeIpoverClaw = 110 // IBM Common Link Access to Workstn + IfTypeStacktostack = 111 // IBM stackToStack + IfTypeVirtualipaddress = 112 // IBM VIPA + IfTypeMpc = 113 // IBM multi-proto channel support + IfTypeIpoverAtm = 114 // IBM ipOverAtm + IfTypeIso88025Fiber = 115 // ISO 802.5j Fiber Token Ring + IfTypeTdlc = 116 // IBM twinaxial data link control + IfTypeGigabitethernet = 117 + IfTypeHdlc = 118 + IfTypeLapF = 119 + IfTypeV37 = 120 + IfTypeX25Mlp = 121 // Multi-Link Protocol + IfTypeX25Huntgroup = 122 // X.25 Hunt Group + IfTypeTransphdlc = 123 + IfTypeInterleave = 124 // Interleave channel + IfTypeFast = 125 // Fast channel + IfTypeIP = 126 // IP (for APPN HPR in IP networks) + IfTypeDocscableMaclayer = 127 // CATV Mac Layer + IfTypeDocscableDownstream = 128 // CATV Downstream interface + IfTypeDocscableUpstream = 129 // CATV Upstream interface + IfTypeA12mppswitch = 130 // Avalon Parallel Processor + IfTypeTunnel = 131 // Encapsulation interface + IfTypeCoffee = 132 // Coffee pot + IfTypeCes = 133 // Circuit Emulation Service + IfTypeAtmSubinterface = 134 // ATM Sub Interface + IfTypeL2Vlan = 135 // Layer 2 Virtual LAN using 802.1Q + IfTypeL3Ipvlan = 136 // Layer 3 Virtual LAN using IP + IfTypeL3Ipxvlan = 137 // Layer 3 Virtual LAN using IPX + IfTypeDigitalpowerline = 138 // IP over Power Lines + IfTypeMediamailoverip = 139 // Multimedia Mail over IP + IfTypeDtm = 140 // Dynamic syncronous Transfer Mode + IfTypeDcn = 141 // Data Communications Network + IfTypeIpforward = 142 // IP Forwarding Interface + IfTypeMsdsl = 143 // Multi-rate Symmetric DSL + IfTypeIEEE1394 = 144 // IEEE1394 High Perf Serial Bus + IfTypeIfGsn = 145 + IfTypeDvbrccMaclayer = 146 + IfTypeDvbrccDownstream = 147 + IfTypeDvbrccUpstream = 148 + IfTypeAtmVirtual = 149 + IfTypeMplsTunnel = 150 + IfTypeSrp = 151 + IfTypeVoiceoveratm = 152 + IfTypeVoiceoverframerelay = 153 + IfTypeIdsl = 154 + IfTypeCompositelink = 155 + IfTypeSs7Siglink = 156 + IfTypePropWirelessP2P = 157 + IfTypeFrForward = 158 + IfTypeRfc1483 = 159 + IfTypeUsb = 160 + IfTypeIEEE8023adLag = 161 + IfTypeBgpPolicyAccounting = 162 + IfTypeFrf16MfrBundle = 163 + IfTypeH323Gatekeeper = 164 + IfTypeH323Proxy = 165 + IfTypeMpls = 166 + IfTypeMfSiglink = 167 + IfTypeHdsl2 = 168 + IfTypeShdsl = 169 + IfTypeDs1Fdl = 170 + IfTypePos = 171 + IfTypeDvbAsiIn = 172 + IfTypeDvbAsiOut = 173 + IfTypePlc = 174 + IfTypeNfas = 175 + IfTypeTr008 = 176 + IfTypeGr303Rdt = 177 + IfTypeGr303Idt = 178 + IfTypeIsup = 179 + IfTypePropDocsWirelessMaclayer = 180 + IfTypePropDocsWirelessDownstream = 181 + IfTypePropDocsWirelessUpstream = 182 + IfTypeHiperlan2 = 183 + IfTypePropBwaP2MP = 184 + IfTypeSonetOverheadChannel = 185 + IfTypeDigitalWrapperOverheadChannel = 186 + IfTypeAal2 = 187 + IfTypeRadioMac = 188 + IfTypeAtmRadio = 189 + IfTypeImt = 190 + IfTypeMvl = 191 + IfTypeReachDsl = 192 + IfTypeFrDlciEndpt = 193 + IfTypeAtmVciEndpt = 194 + IfTypeOpticalChannel = 195 + IfTypeOpticalTransport = 196 + IfTypeIEEE80216Wman = 237 + IfTypeWwanpp = 243 // WWAN devices based on GSM technology + IfTypeWwanpp2 = 244 // WWAN devices based on CDMA technology + IfTypeIEEE802154 = 259 // IEEE 802.15.4 WPAN interface + IfTypeXboxWireless = 281 +) + +// MibIfEntryLevel enumeration specifies level of interface information to retrieve in GetIfTable2Ex function call. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getifentry2ex +type MibIfEntryLevel uint32 + +const ( + MibIfEntryNormal MibIfEntryLevel = 0 + MibIfEntryNormalWithoutStatistics = 2 +) + +// NdisMedium enumeration type identifies the medium types that NDIS drivers support. +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntddndis/ne-ntddndis-_ndis_medium +type NdisMedium uint32 + +const ( + NdisMedium802_3 NdisMedium = iota + NdisMedium802_5 + NdisMediumFddi + NdisMediumWan + NdisMediumLocalTalk + NdisMediumDix // defined for convenience, not a real medium + NdisMediumArcnetRaw + NdisMediumArcnet878_2 + NdisMediumAtm + NdisMediumWirelessWan + NdisMediumIrda + NdisMediumBpc + NdisMediumCoWan + NdisMedium1394 + NdisMediumInfiniBand + NdisMediumTunnel + NdisMediumNative802_11 + NdisMediumLoopback + NdisMediumWiMAX + NdisMediumIP + NdisMediumMax +) + +// NdisPhysicalMedium describes NDIS physical medium type. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_if_row2 +type NdisPhysicalMedium uint32 + +const ( + NdisPhysicalMediumUnspecified NdisPhysicalMedium = iota + NdisPhysicalMediumWirelessLan + NdisPhysicalMediumCableModem + NdisPhysicalMediumPhoneLine + NdisPhysicalMediumPowerLine + NdisPhysicalMediumDSL // includes ADSL and UADSL (G.Lite) + NdisPhysicalMediumFibreChannel + NdisPhysicalMedium1394 + NdisPhysicalMediumWirelessWan + NdisPhysicalMediumNative802_11 + NdisPhysicalMediumBluetooth + NdisPhysicalMediumInfiniband + NdisPhysicalMediumWiMax + NdisPhysicalMediumUWB + NdisPhysicalMedium802_3 + NdisPhysicalMedium802_5 + NdisPhysicalMediumIrda + NdisPhysicalMediumWiredWAN + NdisPhysicalMediumWiredCoWan + NdisPhysicalMediumOther + NdisPhysicalMediumNative802_15_4 + NdisPhysicalMediumMax +) + +// NetIfAccessType enumeration type specifies the NDIS network interface access type. +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-_net_if_access_type +type NetIfAccessType uint32 + +const ( + NetIfAccessLoopback NetIfAccessType = iota + 1 + NetIfAccessBroadcast + NetIfAccessPointToPoint + NetIfAccessPointToMultiPoint + NetIfAccessMax +) + +// NetIfAdminStatus enumeration type specifies the NDIS network interface administrative status, as described in RFC 2863. +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-net_if_admin_status +type NetIfAdminStatus uint32 + +const ( + NetIfAdminStatusUp NetIfAdminStatus = iota + 1 + NetIfAdminStatusDown + NetIfAdminStatusTesting +) + +// NetIfConnectionType enumeration type specifies the NDIS network interface connection type. +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-_net_if_connection_type +type NetIfConnectionType uint32 + +const ( + NetIfConnectionDedicated NetIfConnectionType = iota + 1 + NetIfConnectionPassive + NetIfConnectionDemand + NetIfConnectionMaximum +) + +// NetIfDirectionType enumeration type specifies the NDIS network interface direction type. +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-net_if_direction_type +type NetIfDirectionType uint32 + +const ( + NetIfDirectionSendReceive NetIfDirectionType = iota + NetIfDirectionSendOnly + NetIfDirectionReceiveOnly + NetIfDirectionMaximum +) + +// NetIfMediaConnectState enumeration type specifies the NDIS network interface connection state. +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-_net_if_media_connect_state +type NetIfMediaConnectState uint32 + +const ( + MediaConnectStateUnknown NetIfMediaConnectState = iota + MediaConnectStateConnected + MediaConnectStateDisconnected +) + +// DadState enumeration specifies information about the duplicate address detection (DAD) state for an IPv4 or IPv6 address. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-nl_dad_state +type DadState uint32 + +const ( + DadStateInvalid DadState = iota + DadStateTentative + DadStateDuplicate + DadStateDeprecated + DadStatePreferred +) + +// PrefixOrigin enumeration specifies the origin of an IPv4 or IPv6 address prefix, and is used with the IP_ADAPTER_UNICAST_ADDRESS structure. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-nl_prefix_origin +type PrefixOrigin uint32 + +const ( + PrefixOriginOther PrefixOrigin = iota + PrefixOriginManual + PrefixOriginWellKnown + PrefixOriginDHCP + PrefixOriginRouterAdvertisement + PrefixOriginUnchanged = 1 << 4 +) + +// LinkLocalAddressBehavior enumeration type defines the link local address behavior. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-_nl_link_local_address_behavior +type LinkLocalAddressBehavior int32 + +const ( + LinkLocalAddressAlwaysOff LinkLocalAddressBehavior = iota // Never use link locals. + LinkLocalAddressDelayed // Use link locals only if no other addresses. (default for IPv4). Legacy mapping: IPAutoconfigurationEnabled. + LinkLocalAddressAlwaysOn // Always use link locals (default for IPv6). + LinkLocalAddressUnchanged = -1 +) + +// OffloadRod enumeration specifies a set of flags that indicate the offload capabilities for an IP interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ns-nldef-_nl_interface_offload_rod +type OffloadRod uint8 + +const ( + ChecksumSupported OffloadRod = 1 << iota + OptionsSupported + DatagramChecksumSupported + StreamChecksumSupported + StreamOptionsSupported + FastPathCompatible + LargeSendOffloadSupported + GiantSendOffloadSupported +) + +// RouteOrigin enumeration type defines the origin of the IP route. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-nl_route_origin +type RouteOrigin uint32 + +const ( + RouteOriginManual RouteOrigin = iota + RouteOriginWellKnown + RouteOriginDHCP + RouteOriginRouterAdvertisement + RouteOrigin6to4 +) + +// RouteProtocol enumeration type defines the routing mechanism that an IP route was added with, as described in RFC 4292. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-nl_route_protocol +type RouteProtocol uint32 + +const ( + RouteProtocolOther RouteProtocol = iota + 1 + RouteProtocolLocal + RouteProtocolNetMgmt + RouteProtocolIcmp + RouteProtocolEgp + RouteProtocolGgp + RouteProtocolHello + RouteProtocolRip + RouteProtocolIsIs + RouteProtocolEsIs + RouteProtocolCisco + RouteProtocolBbn + RouteProtocolOspf + RouteProtocolBgp + RouteProtocolIdpr + RouteProtocolEigrp + RouteProtocolDvmrp + RouteProtocolRpl + RouteProtocolDHCP + RouteProtocolNTAutostatic = 10002 + RouteProtocolNTStatic = 10006 + RouteProtocolNTStaticNonDOD = 10007 +) + +// RouterDiscoveryBehavior enumeration type defines the router discovery behavior, as described in RFC 2461. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-_nl_router_discovery_behavior +type RouterDiscoveryBehavior int32 + +const ( + RouterDiscoveryDisabled RouterDiscoveryBehavior = iota + RouterDiscoveryEnabled + RouterDiscoveryDHCP + RouterDiscoveryUnchanged = -1 +) + +// SuffixOrigin enumeration specifies the origin of an IPv4 or IPv6 address suffix, and is used with the IP_ADAPTER_UNICAST_ADDRESS structure. +// https://docs.microsoft.com/en-us/windows/desktop/api/nldef/ne-nldef-nl_suffix_origin +type SuffixOrigin uint32 + +const ( + SuffixOriginOther SuffixOrigin = iota + SuffixOriginManual + SuffixOriginWellKnown + SuffixOriginDHCP + SuffixOriginLinkLayerAddress + SuffixOriginRandom + SuffixOriginUnchanged = 1 << 4 +) + +// MibNotificationType enumeration defines the notification type passed to a callback function when a notification occurs. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ne-netioapi-_mib_notification_type +type MibNotificationType uint32 + +const ( + MibParameterNotification MibNotificationType = iota // Parameter change + MibAddInstance // Addition + MibDeleteInstance // Deletion + MibInitialNotification // Initial notification +) + +type ChangeCallback interface { + Unregister() error +} + +// TunnelType enumeration type defines the encapsulation method used by a tunnel, as described by the Internet Assigned Names Authority (IANA). +// https://docs.microsoft.com/en-us/windows/desktop/api/ifdef/ne-ifdef-tunnel_type +type TunnelType uint32 + +const ( + TunnelTypeNone TunnelType = 0 + TunnelTypeOther = 1 + TunnelTypeDirect = 2 + TunnelType6to4 = 11 + TunnelTypeIsatap = 13 + TunnelTypeTeredo = 14 + TunnelTypeIPHTTPS = 15 +) + +// InterfaceAndOperStatusFlags enumeration type defines interface and operation flags +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_if_row2 +type InterfaceAndOperStatusFlags uint8 + +const ( + IAOSFHardwareInterface InterfaceAndOperStatusFlags = 1 << iota + IAOSFFilterInterface + IAOSFConnectorPresent + IAOSFNotAuthenticated + IAOSFNotMediaConnected + IAOSFPaused + IAOSFLowPower + IAOSFEndPointInterface +) + +// GAAFlags enumeration defines flags used in GetAdaptersAddresses calls +// https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getadaptersaddresses +type GAAFlags uint32 + +const ( + GAAFlagSkipUnicast GAAFlags = 1 << iota + GAAFlagSkipAnycast + GAAFlagSkipMulticast + GAAFlagSkipDNSServer + GAAFlagIncludePrefix + GAAFlagSkipFriendlyName + GAAFlagIncludeWinsInfo + GAAFlagIncludeGateways + GAAFlagIncludeAllInterfaces + GAAFlagIncludeAllCompartments + GAAFlagIncludeTunnelBindingOrder + GAAFlagSkipDNSInfo + + GAAFlagDefault GAAFlags = 0 + GAAFlagSkipAll = GAAFlagSkipUnicast | GAAFlagSkipAnycast | GAAFlagSkipMulticast | GAAFlagSkipDNSServer | GAAFlagSkipFriendlyName | GAAFlagSkipDNSInfo + GAAFlagIncludeAll = GAAFlagIncludePrefix | GAAFlagIncludeWinsInfo | GAAFlagIncludeGateways | GAAFlagIncludeAllInterfaces | GAAFlagIncludeAllCompartments | GAAFlagIncludeTunnelBindingOrder +) + +// ScopeLevel enumeration is used with the IP_ADAPTER_ADDRESSES structure to identify scope levels for IPv6 addresses. +// https://docs.microsoft.com/en-us/windows/desktop/api/ws2def/ne-ws2def-scope_level +type ScopeLevel uint32 + +const ( + ScopeLevelInterface ScopeLevel = 1 + ScopeLevelLink = 2 + ScopeLevelSubnet = 3 + ScopeLevelAdmin = 4 + ScopeLevelSite = 5 + ScopeLevelOrganization = 8 + ScopeLevelGlobal = 14 + ScopeLevelCount = 16 +) + +// RouteData structure describes a route to add +type RouteData struct { + Destination net.IPNet + NextHop net.IP + Metric uint32 +} + +// IPAdapterDNSSuffix structure stores a DNS suffix in a linked list of DNS suffixes for a particular adapter. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_dns_suffix +type IPAdapterDNSSuffix struct { + Next *IPAdapterDNSSuffix + str [maxDNSSuffixStringLength]uint16 +} + +// String method returns the DNS suffix for this DNS suffix entry. +func (obj *IPAdapterDNSSuffix) String() string { + return windows.UTF16ToString(obj.str[:]) +} + +// AdapterName method returns the name of the adapter with which these addresses are associated. +// Unlike an adapter's friendly name, the adapter name returned by AdapterName is permanent and cannot be modified by the user. +func (addr *IPAdapterAddresses) AdapterName() string { + return windows.BytePtrToString(addr.adapterName) +} + +// DNSSuffix method returns adapter DNS suffix associated with this adapter. +func (addr *IPAdapterAddresses) DNSSuffix() string { + if addr.dnsSuffix == nil { + return "" + } + return windows.UTF16PtrToString(addr.dnsSuffix) +} + +// Description method returns description for the adapter. +func (addr *IPAdapterAddresses) Description() string { + if addr.description == nil { + return "" + } + return windows.UTF16PtrToString(addr.description) +} + +// FriendlyName method returns a user-friendly name for the adapter. For example: "Local Area Connection 1." +// This name appears in contexts such as the ipconfig command line program and the Connection folder. +func (addr *IPAdapterAddresses) FriendlyName() string { + if addr.friendlyName == nil { + return "" + } + return windows.UTF16PtrToString(addr.friendlyName) +} + +// PhysicalAddress method returns the Media Access Control (MAC) address for the adapter. +// For example, on an Ethernet network this member would specify the Ethernet hardware address. +func (addr *IPAdapterAddresses) PhysicalAddress() []byte { + return addr.physicalAddress[:addr.physicalAddressLength] +} + +// DHCPv6ClientDUID method returns the DHCP unique identifier (DUID) for the DHCPv6 client. +// This information is only applicable to an IPv6 adapter address configured using DHCPv6. +func (addr *IPAdapterAddresses) DHCPv6ClientDUID() []byte { + return addr.dhcpv6ClientDUID[:addr.dhcpv6ClientDUIDLength] +} + +// Init method initializes the members of an MIB_IPINTERFACE_ROW entry with default values. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-initializeipinterfaceentry +func (row *MibIPInterfaceRow) Init() { + initializeIPInterfaceEntry(row) +} + +// get method retrieves IP information for the specified interface on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipinterfaceentry +func (row *MibIPInterfaceRow) get() error { + if err := getIPInterfaceEntry(row); err != nil { + return err + } + + // Patch that fixes SitePrefixLength issue + // https://stackoverflow.com/questions/54857292/setipinterfaceentry-returns-error-invalid-parameter?noredirect=1 + switch row.Family { + case windows.AF_INET: + if row.SitePrefixLength > 32 { + row.SitePrefixLength = 0 + } + case windows.AF_INET6: + if row.SitePrefixLength > 128 { + row.SitePrefixLength = 128 + } + } + + return nil +} + +// Set method sets the properties of an IP interface on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-setipinterfaceentry +func (row *MibIPInterfaceRow) Set() error { + return setIPInterfaceEntry(row) +} + +// get method returns all table rows as a Go slice. +func (tab *mibIPInterfaceTable) get() (s []MibIPInterfaceRow) { + unsafeSlice(unsafe.Pointer(&s), unsafe.Pointer(&tab.table[0]), int(tab.numEntries)) + return +} + +// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-freemibtable +func (tab *mibIPInterfaceTable) free() { + freeMibTable(unsafe.Pointer(tab)) +} + +// Alias method returns a string that contains the alias name of the network interface. +func (row *MibIfRow2) Alias() string { + return windows.UTF16ToString(row.alias[:]) +} + +// Description method returns a string that contains a description of the network interface. +func (row *MibIfRow2) Description() string { + return windows.UTF16ToString(row.description[:]) +} + +// PhysicalAddress method returns the physical hardware address of the adapter for this network interface. +func (row *MibIfRow2) PhysicalAddress() []byte { + return row.physicalAddress[:row.physicalAddressLength] +} + +// PermanentPhysicalAddress method returns the permanent physical hardware address of the adapter for this network interface. +func (row *MibIfRow2) PermanentPhysicalAddress() []byte { + return row.permanentPhysicalAddress[:row.physicalAddressLength] +} + +// get method retrieves information for the specified interface on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getifentry2 +func (row *MibIfRow2) get() (ret error) { + return getIfEntry2(row) +} + +// get method returns all table rows as a Go slice. +func (tab *mibIfTable2) get() (s []MibIfRow2) { + unsafeSlice(unsafe.Pointer(&s), unsafe.Pointer(&tab.table[0]), int(tab.numEntries)) + return +} + +// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-freemibtable +func (tab *mibIfTable2) free() { + freeMibTable(unsafe.Pointer(tab)) +} + +// RawSockaddrInet union contains an IPv4, an IPv6 address, or an address family. +// https://docs.microsoft.com/en-us/windows/desktop/api/ws2ipdef/ns-ws2ipdef-_sockaddr_inet +type RawSockaddrInet struct { + Family AddressFamily + data [26]byte +} + +// SetIP method sets family, address, and port to the given IPv4 or IPv6 address and port. +// All other members of the structure are set to zero. +func (addr *RawSockaddrInet) SetIP(ip net.IP, port uint16) error { + if v4 := ip.To4(); v4 != nil { + addr4 := (*windows.RawSockaddrInet4)(unsafe.Pointer(addr)) + addr4.Family = windows.AF_INET + copy(addr4.Addr[:], v4) + addr4.Port = port + for i := 0; i < 8; i++ { + addr4.Zero[i] = 0 + } + return nil + } + + if v6 := ip.To16(); v6 != nil { + addr6 := (*windows.RawSockaddrInet6)(unsafe.Pointer(addr)) + addr6.Family = windows.AF_INET6 + addr6.Port = port + addr6.Flowinfo = 0 + copy(addr6.Addr[:], v6) + addr6.Scope_id = 0 + return nil + } + + return windows.ERROR_INVALID_PARAMETER +} + +// IP method returns IPv4 or IPv6 address. +// If the address is neither IPv4 not IPv6 nil is returned. +func (addr *RawSockaddrInet) IP() net.IP { + switch addr.Family { + case windows.AF_INET: + return (*windows.RawSockaddrInet4)(unsafe.Pointer(addr)).Addr[:] + + case windows.AF_INET6: + return (*windows.RawSockaddrInet6)(unsafe.Pointer(addr)).Addr[:] + } + + return nil +} + +// Init method initializes a MibUnicastIPAddressRow structure with default values for a unicast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-initializeunicastipaddressentry +func (row *MibUnicastIPAddressRow) Init() { + initializeUnicastIPAddressEntry(row) +} + +// get method retrieves information for an existing unicast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddressentry +func (row *MibUnicastIPAddressRow) get() error { + return getUnicastIPAddressEntry(row) +} + +// Set method sets the properties of an existing unicast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-setunicastipaddressentry +func (row *MibUnicastIPAddressRow) Set() error { + return setUnicastIPAddressEntry(row) +} + +// Create method adds a new unicast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry +func (row *MibUnicastIPAddressRow) Create() error { + return createUnicastIPAddressEntry(row) +} + +// Delete method deletes an existing unicast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry +func (row *MibUnicastIPAddressRow) Delete() error { + return deleteUnicastIPAddressEntry(row) +} + +// get method returns all table rows as a Go slice. +func (tab *mibUnicastIPAddressTable) get() (s []MibUnicastIPAddressRow) { + unsafeSlice(unsafe.Pointer(&s), unsafe.Pointer(&tab.table[0]), int(tab.numEntries)) + return +} + +// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-freemibtable +func (tab *mibUnicastIPAddressTable) free() { + freeMibTable(unsafe.Pointer(tab)) +} + +// get method retrieves information for an existing anycast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getanycastipaddressentry +func (row *MibAnycastIPAddressRow) get() error { + return getAnycastIPAddressEntry(row) +} + +// Create method adds a new anycast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createanycastipaddressentry +func (row *MibAnycastIPAddressRow) Create() error { + return createAnycastIPAddressEntry(row) +} + +// Delete method deletes an existing anycast IP address entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteanycastipaddressentry +func (row *MibAnycastIPAddressRow) Delete() error { + return deleteAnycastIPAddressEntry(row) +} + +// get method returns all table rows as a Go slice. +func (tab *mibAnycastIPAddressTable) get() (s []MibAnycastIPAddressRow) { + unsafeSlice(unsafe.Pointer(&s), unsafe.Pointer(&tab.table[0]), int(tab.numEntries)) + return +} + +// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-freemibtable +func (tab *mibAnycastIPAddressTable) free() { + freeMibTable(unsafe.Pointer(tab)) +} + +// IPAddressPrefix structure stores an IP address prefix. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_ip_address_prefix +type IPAddressPrefix struct { + Prefix RawSockaddrInet + PrefixLength uint8 + _ [2]byte +} + +// SetIPNet method sets IP address prefix using net.IPNet. +func (prefix *IPAddressPrefix) SetIPNet(net net.IPNet) error { + err := prefix.Prefix.SetIP(net.IP, 0) + if err != nil { + return err + } + ones, _ := net.Mask.Size() + prefix.PrefixLength = uint8(ones) + return nil +} + +// IPNet method returns IP address prefix as net.IPNet. +// If the address is neither IPv4 not IPv6 an empty net.IPNet is returned. The resulting net.IPNet should be checked appropriately. +func (prefix *IPAddressPrefix) IPNet() net.IPNet { + switch prefix.Prefix.Family { + case windows.AF_INET: + return net.IPNet{IP: (*windows.RawSockaddrInet4)(unsafe.Pointer(&prefix.Prefix)).Addr[:], Mask: net.CIDRMask(int(prefix.PrefixLength), 8*net.IPv4len)} + case windows.AF_INET6: + return net.IPNet{IP: (*windows.RawSockaddrInet6)(unsafe.Pointer(&prefix.Prefix)).Addr[:], Mask: net.CIDRMask(int(prefix.PrefixLength), 8*net.IPv6len)} + } + return net.IPNet{} +} + +// MibIPforwardRow2 structure stores information about an IP route entry. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipforward_row2 +type MibIPforwardRow2 struct { + InterfaceLUID LUID + InterfaceIndex uint32 + DestinationPrefix IPAddressPrefix + NextHop RawSockaddrInet + SitePrefixLength uint8 + ValidLifetime uint32 + PreferredLifetime uint32 + Metric uint32 + Protocol RouteProtocol + Loopback bool + AutoconfigureAddress bool + Publish bool + Immortal bool + Age uint32 + Origin RouteOrigin +} + +// Init method initializes a MIB_IPFORWARD_ROW2 structure with default values for an IP route entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-initializeipforwardentry +func (row *MibIPforwardRow2) Init() { + initializeIPForwardEntry(row) +} + +// get method retrieves information for an IP route entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardentry2 +func (row *MibIPforwardRow2) get() error { + return getIPForwardEntry2(row) +} + +// Set method sets the properties of an IP route entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-setipforwardentry2 +func (row *MibIPforwardRow2) Set() error { + return setIPForwardEntry2(row) +} + +// Create method creates a new IP route entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createipforwardentry2 +func (row *MibIPforwardRow2) Create() error { + return createIPForwardEntry2(row) +} + +// Delete method deletes an IP route entry on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteipforwardentry2 +func (row *MibIPforwardRow2) Delete() error { + return deleteIPForwardEntry2(row) +} + +// get method returns all table rows as a Go slice. +func (tab *mibIPforwardTable2) get() (s []MibIPforwardRow2) { + unsafeSlice(unsafe.Pointer(&s), unsafe.Pointer(&tab.table[0]), int(tab.numEntries)) + return +} + +// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-freemibtable +func (tab *mibIPforwardTable2) free() { + freeMibTable(unsafe.Pointer(tab)) +} + +// +// Undocumented DNS API +// + +// dnsInterfaceSettings is mean to be used with setInterfaceDnsSettings +type dnsInterfaceSettings struct { + Version uint32 + _ [4]byte + Flags uint64 + Domain *uint16 + NameServer *uint16 + SearchList *uint16 + RegistrationEnabled uint32 + RegisterAdapterName uint32 + EnableLLMNR uint32 + QueryAdapterName uint32 + ProfileNameServer *uint16 +} + +const ( + disVersion1 = 1 + disVersion2 = 2 + + disFlagsIPv6 = 0x1 + disFlagsNameServer = 0x2 + disFlagsSearchList = 0x4 + disFlagsRegistrationEnabled = 0x8 + disFlagsRegisterAdapterName = 0x10 + disFlagsDomain = 0x20 + disFlagsHostname = 0x40 // ?? + disFlagsEnableLLMNR = 0x80 + disFlagsQueryAdapterName = 0x100 + disFlagsProfileNameServer = 0x200 + disFlagsVersion2 = 0x400 // ?? - v2 only + disFlagsMoreFlags = 0x800 // ?? - v2 only +) + +// unsafeSlice updates the slice slicePtr to be a slice +// referencing the provided data with its length & capacity set to +// lenCap. +// +// TODO: when Go 1.16 or Go 1.17 is the minimum supported version, +// update callers to use unsafe.Slice instead of this. +func unsafeSlice(slicePtr, data unsafe.Pointer, lenCap int) { + type sliceHeader struct { + Data unsafe.Pointer + Len int + Cap int + } + h := (*sliceHeader)(slicePtr) + h.Data = data + h.Len = lenCap + h.Cap = lenCap +} diff --git a/listener/tun/dev/winipcfg/types_32.go b/listener/tun/dev/winipcfg/types_32.go new file mode 100644 index 00000000..5293aebf --- /dev/null +++ b/listener/tun/dev/winipcfg/types_32.go @@ -0,0 +1,227 @@ +// +build 386 arm + +package winipcfg + +import ( + "golang.org/x/sys/windows" +) + +// IPAdapterWINSServerAddress structure stores a single Windows Internet Name Service (WINS) server address in a linked list of WINS server addresses for a particular adapter. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_wins_server_address_lh +type IPAdapterWINSServerAddress struct { + Length uint32 + _ uint32 + Next *IPAdapterWINSServerAddress + Address windows.SocketAddress + _ [4]byte +} + +// IPAdapterGatewayAddress structure stores a single gateway address in a linked list of gateway addresses for a particular adapter. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_gateway_address_lh +type IPAdapterGatewayAddress struct { + Length uint32 + _ uint32 + Next *IPAdapterGatewayAddress + Address windows.SocketAddress + _ [4]byte +} + +// IPAdapterAddresses structure is the header node for a linked list of addresses for a particular adapter. This structure can simultaneously be used as part of a linked list of IP_ADAPTER_ADDRESSES structures. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_addresses_lh +// This is a modified and extended version of windows.IpAdapterAddresses. +type IPAdapterAddresses struct { + Length uint32 + IfIndex uint32 + Next *IPAdapterAddresses + adapterName *byte + FirstUnicastAddress *windows.IpAdapterUnicastAddress + FirstAnycastAddress *windows.IpAdapterAnycastAddress + FirstMulticastAddress *windows.IpAdapterMulticastAddress + FirstDNSServerAddress *windows.IpAdapterDnsServerAdapter + dnsSuffix *uint16 + description *uint16 + friendlyName *uint16 + physicalAddress [windows.MAX_ADAPTER_ADDRESS_LENGTH]byte + physicalAddressLength uint32 + Flags IPAAFlags + MTU uint32 + IfType IfType + OperStatus IfOperStatus + IPv6IfIndex uint32 + ZoneIndices [16]uint32 + FirstPrefix *windows.IpAdapterPrefix + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + FirstWINSServerAddress *IPAdapterWINSServerAddress + FirstGatewayAddress *IPAdapterGatewayAddress + Ipv4Metric uint32 + Ipv6Metric uint32 + LUID LUID + DHCPv4Server windows.SocketAddress + CompartmentID uint32 + NetworkGUID windows.GUID + ConnectionType NetIfConnectionType + TunnelType TunnelType + DHCPv6Server windows.SocketAddress + dhcpv6ClientDUID [maxDHCPv6DUIDLength]byte + dhcpv6ClientDUIDLength uint32 + DHCPv6IAID uint32 + FirstDNSSuffix *IPAdapterDNSSuffix + _ [4]byte +} + +// MibIPInterfaceRow structure stores interface management information for a particular IP address family on a network interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipinterface_row +type MibIPInterfaceRow struct { + Family AddressFamily + _ [4]byte + InterfaceLUID LUID + InterfaceIndex uint32 + MaxReassemblySize uint32 + InterfaceIdentifier uint64 + MinRouterAdvertisementInterval uint32 + MaxRouterAdvertisementInterval uint32 + AdvertisingEnabled bool + ForwardingEnabled bool + WeakHostSend bool + WeakHostReceive bool + UseAutomaticMetric bool + UseNeighborUnreachabilityDetection bool + ManagedAddressConfigurationSupported bool + OtherStatefulConfigurationSupported bool + AdvertiseDefaultRoute bool + RouterDiscoveryBehavior RouterDiscoveryBehavior + DadTransmits uint32 + BaseReachableTime uint32 + RetransmitTime uint32 + PathMTUDiscoveryTimeout uint32 + LinkLocalAddressBehavior LinkLocalAddressBehavior + LinkLocalAddressTimeout uint32 + ZoneIndices [ScopeLevelCount]uint32 + SitePrefixLength uint32 + Metric uint32 + NLMTU uint32 + Connected bool + SupportsWakeUpPatterns bool + SupportsNeighborDiscovery bool + SupportsRouterDiscovery bool + ReachableTime uint32 + TransmitOffload OffloadRod + ReceiveOffload OffloadRod + DisableDefaultRoutes bool +} + +// mibIPInterfaceTable structure contains a table of IP interface entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipinterface_table +type mibIPInterfaceTable struct { + numEntries uint32 + _ [4]byte + table [anySize]MibIPInterfaceRow +} + +// MibIfRow2 structure stores information about a particular interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_if_row2 +type MibIfRow2 struct { + InterfaceLUID LUID + InterfaceIndex uint32 + InterfaceGUID windows.GUID + alias [ifMaxStringSize + 1]uint16 + description [ifMaxStringSize + 1]uint16 + physicalAddressLength uint32 + physicalAddress [ifMaxPhysAddressLength]byte + permanentPhysicalAddress [ifMaxPhysAddressLength]byte + MTU uint32 + Type IfType + TunnelType TunnelType + MediaType NdisMedium + PhysicalMediumType NdisPhysicalMedium + AccessType NetIfAccessType + DirectionType NetIfDirectionType + InterfaceAndOperStatusFlags InterfaceAndOperStatusFlags + OperStatus IfOperStatus + AdminStatus NetIfAdminStatus + MediaConnectState NetIfMediaConnectState + NetworkGUID windows.GUID + ConnectionType NetIfConnectionType + _ [4]byte + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} + +// mibIfTable2 structure contains a table of logical and physical interface entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_if_table2 +type mibIfTable2 struct { + numEntries uint32 + _ [4]byte + table [anySize]MibIfRow2 +} + +// MibUnicastIPAddressRow structure stores information about a unicast IP address. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_unicastipaddress_row +type MibUnicastIPAddressRow struct { + Address RawSockaddrInet + _ [4]byte + InterfaceLUID LUID + InterfaceIndex uint32 + PrefixOrigin PrefixOrigin + SuffixOrigin SuffixOrigin + ValidLifetime uint32 + PreferredLifetime uint32 + OnLinkPrefixLength uint8 + SkipAsSource bool + DadState DadState + ScopeID uint32 + CreationTimeStamp int64 +} + +// mibUnicastIPAddressTable structure contains a table of unicast IP address entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_unicastipaddress_table +type mibUnicastIPAddressTable struct { + numEntries uint32 + _ [4]byte + table [anySize]MibUnicastIPAddressRow +} + +// MibAnycastIPAddressRow structure stores information about an anycast IP address. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_anycastipaddress_row +type MibAnycastIPAddressRow struct { + Address RawSockaddrInet + _ [4]byte + InterfaceLUID LUID + InterfaceIndex uint32 + ScopeID uint32 +} + +// mibAnycastIPAddressTable structure contains a table of anycast IP address entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-mib_anycastipaddress_table +type mibAnycastIPAddressTable struct { + numEntries uint32 + _ [4]byte + table [anySize]MibAnycastIPAddressRow +} + +// mibIPforwardTable2 structure contains a table of IP route entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipforward_table2 +type mibIPforwardTable2 struct { + numEntries uint32 + _ [4]byte + table [anySize]MibIPforwardRow2 +} diff --git a/listener/tun/dev/winipcfg/types_64.go b/listener/tun/dev/winipcfg/types_64.go new file mode 100644 index 00000000..a3876060 --- /dev/null +++ b/listener/tun/dev/winipcfg/types_64.go @@ -0,0 +1,216 @@ +// +build windows +// +build amd64 arm64 + +package winipcfg + +import ( + "golang.org/x/sys/windows" +) + +// IPAdapterWINSServerAddress structure stores a single Windows Internet Name Service (WINS) server address in a linked list of WINS server addresses for a particular adapter. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_wins_server_address_lh +type IPAdapterWINSServerAddress struct { + Length uint32 + _ uint32 + Next *IPAdapterWINSServerAddress + Address windows.SocketAddress +} + +// IPAdapterGatewayAddress structure stores a single gateway address in a linked list of gateway addresses for a particular adapter. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_gateway_address_lh +type IPAdapterGatewayAddress struct { + Length uint32 + _ uint32 + Next *IPAdapterGatewayAddress + Address windows.SocketAddress +} + +// IPAdapterAddresses structure is the header node for a linked list of addresses for a particular adapter. This structure can simultaneously be used as part of a linked list of IP_ADAPTER_ADDRESSES structures. +// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_addresses_lh +// This is a modified and extended version of windows.IpAdapterAddresses. +type IPAdapterAddresses struct { + Length uint32 + IfIndex uint32 + Next *IPAdapterAddresses + adapterName *byte + FirstUnicastAddress *windows.IpAdapterUnicastAddress + FirstAnycastAddress *windows.IpAdapterAnycastAddress + FirstMulticastAddress *windows.IpAdapterMulticastAddress + FirstDNSServerAddress *windows.IpAdapterDnsServerAdapter + dnsSuffix *uint16 + description *uint16 + friendlyName *uint16 + physicalAddress [windows.MAX_ADAPTER_ADDRESS_LENGTH]byte + physicalAddressLength uint32 + Flags IPAAFlags + MTU uint32 + IfType IfType + OperStatus IfOperStatus + IPv6IfIndex uint32 + ZoneIndices [16]uint32 + FirstPrefix *windows.IpAdapterPrefix + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + FirstWINSServerAddress *IPAdapterWINSServerAddress + FirstGatewayAddress *IPAdapterGatewayAddress + Ipv4Metric uint32 + Ipv6Metric uint32 + LUID LUID + DHCPv4Server windows.SocketAddress + CompartmentID uint32 + NetworkGUID windows.GUID + ConnectionType NetIfConnectionType + TunnelType TunnelType + DHCPv6Server windows.SocketAddress + dhcpv6ClientDUID [maxDHCPv6DUIDLength]byte + dhcpv6ClientDUIDLength uint32 + DHCPv6IAID uint32 + FirstDNSSuffix *IPAdapterDNSSuffix +} + +// MibIPInterfaceRow structure stores interface management information for a particular IP address family on a network interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipinterface_row +type MibIPInterfaceRow struct { + Family AddressFamily + InterfaceLUID LUID + InterfaceIndex uint32 + MaxReassemblySize uint32 + InterfaceIdentifier uint64 + MinRouterAdvertisementInterval uint32 + MaxRouterAdvertisementInterval uint32 + AdvertisingEnabled bool + ForwardingEnabled bool + WeakHostSend bool + WeakHostReceive bool + UseAutomaticMetric bool + UseNeighborUnreachabilityDetection bool + ManagedAddressConfigurationSupported bool + OtherStatefulConfigurationSupported bool + AdvertiseDefaultRoute bool + RouterDiscoveryBehavior RouterDiscoveryBehavior + DadTransmits uint32 + BaseReachableTime uint32 + RetransmitTime uint32 + PathMTUDiscoveryTimeout uint32 + LinkLocalAddressBehavior LinkLocalAddressBehavior + LinkLocalAddressTimeout uint32 + ZoneIndices [ScopeLevelCount]uint32 + SitePrefixLength uint32 + Metric uint32 + NLMTU uint32 + Connected bool + SupportsWakeUpPatterns bool + SupportsNeighborDiscovery bool + SupportsRouterDiscovery bool + ReachableTime uint32 + TransmitOffload OffloadRod + ReceiveOffload OffloadRod + DisableDefaultRoutes bool +} + +// mibIPInterfaceTable structure contains a table of IP interface entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipinterface_table +type mibIPInterfaceTable struct { + numEntries uint32 + table [anySize]MibIPInterfaceRow +} + +// MibIfRow2 structure stores information about a particular interface. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_if_row2 +type MibIfRow2 struct { + InterfaceLUID LUID + InterfaceIndex uint32 + InterfaceGUID windows.GUID + alias [ifMaxStringSize + 1]uint16 + description [ifMaxStringSize + 1]uint16 + physicalAddressLength uint32 + physicalAddress [ifMaxPhysAddressLength]byte + permanentPhysicalAddress [ifMaxPhysAddressLength]byte + MTU uint32 + Type IfType + TunnelType TunnelType + MediaType NdisMedium + PhysicalMediumType NdisPhysicalMedium + AccessType NetIfAccessType + DirectionType NetIfDirectionType + InterfaceAndOperStatusFlags InterfaceAndOperStatusFlags + OperStatus IfOperStatus + AdminStatus NetIfAdminStatus + MediaConnectState NetIfMediaConnectState + NetworkGUID windows.GUID + ConnectionType NetIfConnectionType + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} + +// mibIfTable2 structure contains a table of logical and physical interface entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_if_table2 +type mibIfTable2 struct { + numEntries uint32 + table [anySize]MibIfRow2 +} + +// MibUnicastIPAddressRow structure stores information about a unicast IP address. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_unicastipaddress_row +type MibUnicastIPAddressRow struct { + Address RawSockaddrInet + InterfaceLUID LUID + InterfaceIndex uint32 + PrefixOrigin PrefixOrigin + SuffixOrigin SuffixOrigin + ValidLifetime uint32 + PreferredLifetime uint32 + OnLinkPrefixLength uint8 + SkipAsSource bool + DadState DadState + ScopeID uint32 + CreationTimeStamp int64 +} + +// mibUnicastIPAddressTable structure contains a table of unicast IP address entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_unicastipaddress_table +type mibUnicastIPAddressTable struct { + numEntries uint32 + table [anySize]MibUnicastIPAddressRow +} + +// MibAnycastIPAddressRow structure stores information about an anycast IP address. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_anycastipaddress_row +type MibAnycastIPAddressRow struct { + Address RawSockaddrInet + InterfaceLUID LUID + InterfaceIndex uint32 + ScopeID uint32 +} + +// mibAnycastIPAddressTable structure contains a table of anycast IP address entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-mib_anycastipaddress_table +type mibAnycastIPAddressTable struct { + numEntries uint32 + table [anySize]MibAnycastIPAddressRow +} + +// mibIPforwardTable2 structure contains a table of IP route entries. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_mib_ipforward_table2 +type mibIPforwardTable2 struct { + numEntries uint32 + table [anySize]MibIPforwardRow2 +} diff --git a/listener/tun/dev/winipcfg/unicast_address_change_handler.go b/listener/tun/dev/winipcfg/unicast_address_change_handler.go new file mode 100644 index 00000000..3ab468e3 --- /dev/null +++ b/listener/tun/dev/winipcfg/unicast_address_change_handler.go @@ -0,0 +1,85 @@ +// +build windows + +package winipcfg + +import ( + "sync" + + "golang.org/x/sys/windows" +) + +// UnicastAddressChangeCallback structure allows unicast address change callback handling. +type UnicastAddressChangeCallback struct { + cb func(notificationType MibNotificationType, unicastAddress *MibUnicastIPAddressRow) + wait sync.WaitGroup +} + +var ( + unicastAddressChangeAddRemoveMutex = sync.Mutex{} + unicastAddressChangeMutex = sync.Mutex{} + unicastAddressChangeCallbacks = make(map[*UnicastAddressChangeCallback]bool) + unicastAddressChangeHandle = windows.Handle(0) +) + +// RegisterUnicastAddressChangeCallback registers a new UnicastAddressChangeCallback. If this particular callback is already +// registered, the function will silently return. Returned UnicastAddressChangeCallback.Unregister method should be used +// to unregister. +func RegisterUnicastAddressChangeCallback(callback func(notificationType MibNotificationType, unicastAddress *MibUnicastIPAddressRow)) (*UnicastAddressChangeCallback, error) { + s := &UnicastAddressChangeCallback{cb: callback} + + unicastAddressChangeAddRemoveMutex.Lock() + defer unicastAddressChangeAddRemoveMutex.Unlock() + + unicastAddressChangeMutex.Lock() + defer unicastAddressChangeMutex.Unlock() + + unicastAddressChangeCallbacks[s] = true + + if unicastAddressChangeHandle == 0 { + err := notifyUnicastIPAddressChange(windows.AF_UNSPEC, windows.NewCallback(unicastAddressChanged), 0, false, &unicastAddressChangeHandle) + if err != nil { + delete(unicastAddressChangeCallbacks, s) + unicastAddressChangeHandle = 0 + return nil, err + } + } + + return s, nil +} + +// Unregister unregisters the callback. +func (callback *UnicastAddressChangeCallback) Unregister() error { + unicastAddressChangeAddRemoveMutex.Lock() + defer unicastAddressChangeAddRemoveMutex.Unlock() + + unicastAddressChangeMutex.Lock() + delete(unicastAddressChangeCallbacks, callback) + removeIt := len(unicastAddressChangeCallbacks) == 0 && unicastAddressChangeHandle != 0 + unicastAddressChangeMutex.Unlock() + + callback.wait.Wait() + + if removeIt { + err := cancelMibChangeNotify2(unicastAddressChangeHandle) + if err != nil { + return err + } + unicastAddressChangeHandle = 0 + } + + return nil +} + +func unicastAddressChanged(callerContext uintptr, row *MibUnicastIPAddressRow, notificationType MibNotificationType) uintptr { + rowCopy := *row + unicastAddressChangeMutex.Lock() + for cb := range unicastAddressChangeCallbacks { + cb.wait.Add(1) + go func(cb *UnicastAddressChangeCallback) { + cb.cb(notificationType, &rowCopy) + cb.wait.Done() + }(cb) + } + unicastAddressChangeMutex.Unlock() + return 0 +} diff --git a/listener/tun/dev/winipcfg/winipcfg.go b/listener/tun/dev/winipcfg/winipcfg.go new file mode 100644 index 00000000..e96f133b --- /dev/null +++ b/listener/tun/dev/winipcfg/winipcfg.go @@ -0,0 +1,193 @@ +// +build windows + +package winipcfg + +import ( + "runtime" + "unsafe" + + "golang.org/x/sys/windows" +) + +// +// Common functions +// + +//sys freeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable + +// +// Interface-related functions +// + +//sys initializeIPInterfaceEntry(row *MibIPInterfaceRow) = iphlpapi.InitializeIpInterfaceEntry +//sys getIPInterfaceTable(family AddressFamily, table **mibIPInterfaceTable) (ret error) = iphlpapi.GetIpInterfaceTable +//sys getIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) = iphlpapi.GetIpInterfaceEntry +//sys setIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) = iphlpapi.SetIpInterfaceEntry +//sys getIfEntry2(row *MibIfRow2) (ret error) = iphlpapi.GetIfEntry2 +//sys getIfTable2Ex(level MibIfEntryLevel, table **mibIfTable2) (ret error) = iphlpapi.GetIfTable2Ex +//sys convertInterfaceLUIDToGUID(interfaceLUID *LUID, interfaceGUID *windows.GUID) (ret error) = iphlpapi.ConvertInterfaceLuidToGuid +//sys convertInterfaceGUIDToLUID(interfaceGUID *windows.GUID, interfaceLUID *LUID) (ret error) = iphlpapi.ConvertInterfaceGuidToLuid +//sys convertInterfaceIndexToLUID(interfaceIndex uint32, interfaceLUID *LUID) (ret error) = iphlpapi.ConvertInterfaceIndexToLuid + +// GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getadaptersaddresses +func GetAdaptersAddresses(family AddressFamily, flags GAAFlags) ([]*IPAdapterAddresses, error) { + var b []byte + size := uint32(15000) + + for { + b = make([]byte, size) + err := windows.GetAdaptersAddresses(uint32(family), uint32(flags), 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &size) + if err == nil { + break + } + if err != windows.ERROR_BUFFER_OVERFLOW || size <= uint32(len(b)) { + return nil, err + } + } + + result := make([]*IPAdapterAddresses, 0, uintptr(size)/unsafe.Sizeof(IPAdapterAddresses{})) + for wtiaa := (*IPAdapterAddresses)(unsafe.Pointer(&b[0])); wtiaa != nil; wtiaa = wtiaa.Next { + result = append(result, wtiaa) + } + + return result, nil +} + +// GetIPInterfaceTable function retrieves the IP interface entries on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipinterfacetable +func GetIPInterfaceTable(family AddressFamily) ([]MibIPInterfaceRow, error) { + var tab *mibIPInterfaceTable + err := getIPInterfaceTable(family, &tab) + if err != nil { + return nil, err + } + t := append(make([]MibIPInterfaceRow, 0, tab.numEntries), tab.get()...) + tab.free() + return t, nil +} + +// GetIfTable2Ex function retrieves the MIB-II interface table. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getiftable2ex +func GetIfTable2Ex(level MibIfEntryLevel) ([]MibIfRow2, error) { + var tab *mibIfTable2 + err := getIfTable2Ex(level, &tab) + if err != nil { + return nil, err + } + t := append(make([]MibIfRow2, 0, tab.numEntries), tab.get()...) + tab.free() + return t, nil +} + +// +// Unicast IP address-related functions +// + +//sys getUnicastIPAddressTable(family AddressFamily, table **mibUnicastIPAddressTable) (ret error) = iphlpapi.GetUnicastIpAddressTable +//sys initializeUnicastIPAddressEntry(row *MibUnicastIPAddressRow) = iphlpapi.InitializeUnicastIpAddressEntry +//sys getUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.GetUnicastIpAddressEntry +//sys setUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.SetUnicastIpAddressEntry +//sys createUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.CreateUnicastIpAddressEntry +//sys deleteUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.DeleteUnicastIpAddressEntry + +// GetUnicastIPAddressTable function retrieves the unicast IP address table on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddresstable +func GetUnicastIPAddressTable(family AddressFamily) ([]MibUnicastIPAddressRow, error) { + var tab *mibUnicastIPAddressTable + err := getUnicastIPAddressTable(family, &tab) + if err != nil { + return nil, err + } + t := append(make([]MibUnicastIPAddressRow, 0, tab.numEntries), tab.get()...) + tab.free() + return t, nil +} + +// +// Anycast IP address-related functions +// + +//sys getAnycastIPAddressTable(family AddressFamily, table **mibAnycastIPAddressTable) (ret error) = iphlpapi.GetAnycastIpAddressTable +//sys getAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) = iphlpapi.GetAnycastIpAddressEntry +//sys createAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) = iphlpapi.CreateAnycastIpAddressEntry +//sys deleteAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) = iphlpapi.DeleteAnycastIpAddressEntry + +// GetAnycastIPAddressTable function retrieves the anycast IP address table on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getanycastipaddresstable +func GetAnycastIPAddressTable(family AddressFamily) ([]MibAnycastIPAddressRow, error) { + var tab *mibAnycastIPAddressTable + err := getAnycastIPAddressTable(family, &tab) + if err != nil { + return nil, err + } + t := append(make([]MibAnycastIPAddressRow, 0, tab.numEntries), tab.get()...) + tab.free() + return t, nil +} + +// +// Routing-related functions +// + +//sys getIPForwardTable2(family AddressFamily, table **mibIPforwardTable2) (ret error) = iphlpapi.GetIpForwardTable2 +//sys initializeIPForwardEntry(route *MibIPforwardRow2) = iphlpapi.InitializeIpForwardEntry +//sys getIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.GetIpForwardEntry2 +//sys setIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.SetIpForwardEntry2 +//sys createIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.CreateIpForwardEntry2 +//sys deleteIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.DeleteIpForwardEntry2 + +// GetIPForwardTable2 function retrieves the IP route entries on the local computer. +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardtable2 +func GetIPForwardTable2(family AddressFamily) ([]MibIPforwardRow2, error) { + var tab *mibIPforwardTable2 + err := getIPForwardTable2(family, &tab) + if err != nil { + return nil, err + } + t := append(make([]MibIPforwardRow2, 0, tab.numEntries), tab.get()...) + tab.free() + return t, nil +} + +// +// Notifications-related functions +// + +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-notifyipinterfacechange +//sys notifyIPInterfaceChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) = iphlpapi.NotifyIpInterfaceChange + +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-notifyunicastipaddresschange +//sys notifyUnicastIPAddressChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) = iphlpapi.NotifyUnicastIpAddressChange + +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-notifyroutechange2 +//sys notifyRouteChange2(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) = iphlpapi.NotifyRouteChange2 + +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-cancelmibchangenotify2 +//sys cancelMibChangeNotify2(notificationHandle windows.Handle) (ret error) = iphlpapi.CancelMibChangeNotify2 + +// +// Undocumented DNS API +// + +//sys setInterfaceDnsSettingsByPtr(guid *windows.GUID, settings *dnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings? +//sys setInterfaceDnsSettingsByQwords(guid1 uintptr, guid2 uintptr, settings *dnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings? +//sys setInterfaceDnsSettingsByDwords(guid1 uintptr, guid2 uintptr, guid3 uintptr, guid4 uintptr, settings *dnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings? + +// The GUID is passed by value, not by reference, which means different +// things on different calling conventions. On amd64, this means it's +// passed by reference anyway, while on arm, arm64, and 386, it's split +// into words. +func setInterfaceDnsSettings(guid windows.GUID, settings *dnsInterfaceSettings) error { + words := (*[4]uintptr)(unsafe.Pointer(&guid)) + switch runtime.GOARCH { + case "amd64": + return setInterfaceDnsSettingsByPtr(&guid, settings) + case "arm64": + return setInterfaceDnsSettingsByQwords(words[0], words[1], settings) + case "arm", "386": + return setInterfaceDnsSettingsByDwords(words[0], words[1], words[2], words[3], settings) + default: + panic("unknown calling convention") + } +} diff --git a/listener/tun/dev/winipcfg/zwinipcfg_windows.go b/listener/tun/dev/winipcfg/zwinipcfg_windows.go new file mode 100644 index 00000000..ac89fec1 --- /dev/null +++ b/listener/tun/dev/winipcfg/zwinipcfg_windows.go @@ -0,0 +1,350 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package winipcfg + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") + + procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2") + procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid") + procConvertInterfaceIndexToLuid = modiphlpapi.NewProc("ConvertInterfaceIndexToLuid") + procConvertInterfaceLuidToGuid = modiphlpapi.NewProc("ConvertInterfaceLuidToGuid") + procCreateAnycastIpAddressEntry = modiphlpapi.NewProc("CreateAnycastIpAddressEntry") + procCreateIpForwardEntry2 = modiphlpapi.NewProc("CreateIpForwardEntry2") + procCreateUnicastIpAddressEntry = modiphlpapi.NewProc("CreateUnicastIpAddressEntry") + procDeleteAnycastIpAddressEntry = modiphlpapi.NewProc("DeleteAnycastIpAddressEntry") + procDeleteIpForwardEntry2 = modiphlpapi.NewProc("DeleteIpForwardEntry2") + procDeleteUnicastIpAddressEntry = modiphlpapi.NewProc("DeleteUnicastIpAddressEntry") + procFreeMibTable = modiphlpapi.NewProc("FreeMibTable") + procGetAnycastIpAddressEntry = modiphlpapi.NewProc("GetAnycastIpAddressEntry") + procGetAnycastIpAddressTable = modiphlpapi.NewProc("GetAnycastIpAddressTable") + procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2") + procGetIfTable2Ex = modiphlpapi.NewProc("GetIfTable2Ex") + procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2") + procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2") + procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry") + procGetIpInterfaceTable = modiphlpapi.NewProc("GetIpInterfaceTable") + procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry") + procGetUnicastIpAddressTable = modiphlpapi.NewProc("GetUnicastIpAddressTable") + procInitializeIpForwardEntry = modiphlpapi.NewProc("InitializeIpForwardEntry") + procInitializeIpInterfaceEntry = modiphlpapi.NewProc("InitializeIpInterfaceEntry") + procInitializeUnicastIpAddressEntry = modiphlpapi.NewProc("InitializeUnicastIpAddressEntry") + procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange") + procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2") + procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange") + procSetInterfaceDnsSettings = modiphlpapi.NewProc("SetInterfaceDnsSettings") + procSetIpForwardEntry2 = modiphlpapi.NewProc("SetIpForwardEntry2") + procSetIpInterfaceEntry = modiphlpapi.NewProc("SetIpInterfaceEntry") + procSetUnicastIpAddressEntry = modiphlpapi.NewProc("SetUnicastIpAddressEntry") +) + +func cancelMibChangeNotify2(notificationHandle windows.Handle) (ret error) { + r0, _, _ := syscall.Syscall(procCancelMibChangeNotify2.Addr(), 1, uintptr(notificationHandle), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func convertInterfaceGUIDToLUID(interfaceGUID *windows.GUID, interfaceLUID *LUID) (ret error) { + r0, _, _ := syscall.Syscall(procConvertInterfaceGuidToLuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceGUID)), uintptr(unsafe.Pointer(interfaceLUID)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func convertInterfaceIndexToLUID(interfaceIndex uint32, interfaceLUID *LUID) (ret error) { + r0, _, _ := syscall.Syscall(procConvertInterfaceIndexToLuid.Addr(), 2, uintptr(interfaceIndex), uintptr(unsafe.Pointer(interfaceLUID)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func convertInterfaceLUIDToGUID(interfaceLUID *LUID, interfaceGUID *windows.GUID) (ret error) { + r0, _, _ := syscall.Syscall(procConvertInterfaceLuidToGuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceLUID)), uintptr(unsafe.Pointer(interfaceGUID)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func createAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procCreateAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func createIPForwardEntry2(route *MibIPforwardRow2) (ret error) { + r0, _, _ := syscall.Syscall(procCreateIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func createUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procCreateUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func deleteAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procDeleteAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func deleteIPForwardEntry2(route *MibIPforwardRow2) (ret error) { + r0, _, _ := syscall.Syscall(procDeleteIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func deleteUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procDeleteUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func freeMibTable(memory unsafe.Pointer) { + syscall.Syscall(procFreeMibTable.Addr(), 1, uintptr(memory), 0, 0) + return +} + +func getAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procGetAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getAnycastIPAddressTable(family AddressFamily, table **mibAnycastIPAddressTable) (ret error) { + r0, _, _ := syscall.Syscall(procGetAnycastIpAddressTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIfEntry2(row *MibIfRow2) (ret error) { + r0, _, _ := syscall.Syscall(procGetIfEntry2.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIfTable2Ex(level MibIfEntryLevel, table **mibIfTable2) (ret error) { + r0, _, _ := syscall.Syscall(procGetIfTable2Ex.Addr(), 2, uintptr(level), uintptr(unsafe.Pointer(table)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIPForwardEntry2(route *MibIPforwardRow2) (ret error) { + r0, _, _ := syscall.Syscall(procGetIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIPForwardTable2(family AddressFamily, table **mibIPforwardTable2) (ret error) { + r0, _, _ := syscall.Syscall(procGetIpForwardTable2.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) { + r0, _, _ := syscall.Syscall(procGetIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIPInterfaceTable(family AddressFamily, table **mibIPInterfaceTable) (ret error) { + r0, _, _ := syscall.Syscall(procGetIpInterfaceTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procGetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getUnicastIPAddressTable(family AddressFamily, table **mibUnicastIPAddressTable) (ret error) { + r0, _, _ := syscall.Syscall(procGetUnicastIpAddressTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func initializeIPForwardEntry(route *MibIPforwardRow2) { + syscall.Syscall(procInitializeIpForwardEntry.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0) + return +} + +func initializeIPInterfaceEntry(row *MibIPInterfaceRow) { + syscall.Syscall(procInitializeIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + return +} + +func initializeUnicastIPAddressEntry(row *MibUnicastIPAddressRow) { + syscall.Syscall(procInitializeUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + return +} + +func notifyIPInterfaceChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) { + var _p0 uint32 + if initialNotification { + _p0 = 1 + } + r0, _, _ := syscall.Syscall6(procNotifyIpInterfaceChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func notifyRouteChange2(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) { + var _p0 uint32 + if initialNotification { + _p0 = 1 + } + r0, _, _ := syscall.Syscall6(procNotifyRouteChange2.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func notifyUnicastIPAddressChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) { + var _p0 uint32 + if initialNotification { + _p0 = 1 + } + r0, _, _ := syscall.Syscall6(procNotifyUnicastIpAddressChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func setInterfaceDnsSettingsByDwords(guid1 uintptr, guid2 uintptr, guid3 uintptr, guid4 uintptr, settings *dnsInterfaceSettings) (ret error) { + ret = procSetInterfaceDnsSettings.Find() + if ret != nil { + return + } + r0, _, _ := syscall.Syscall6(procSetInterfaceDnsSettings.Addr(), 5, uintptr(guid1), uintptr(guid2), uintptr(guid3), uintptr(guid4), uintptr(unsafe.Pointer(settings)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func setInterfaceDnsSettingsByPtr(guid *windows.GUID, settings *dnsInterfaceSettings) (ret error) { + ret = procSetInterfaceDnsSettings.Find() + if ret != nil { + return + } + r0, _, _ := syscall.Syscall(procSetInterfaceDnsSettings.Addr(), 2, uintptr(unsafe.Pointer(guid)), uintptr(unsafe.Pointer(settings)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func setInterfaceDnsSettingsByQwords(guid1 uintptr, guid2 uintptr, settings *dnsInterfaceSettings) (ret error) { + ret = procSetInterfaceDnsSettings.Find() + if ret != nil { + return + } + r0, _, _ := syscall.Syscall(procSetInterfaceDnsSettings.Addr(), 3, uintptr(guid1), uintptr(guid2), uintptr(unsafe.Pointer(settings))) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func setIPForwardEntry2(route *MibIPforwardRow2) (ret error) { + r0, _, _ := syscall.Syscall(procSetIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func setIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) { + r0, _, _ := syscall.Syscall(procSetIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func setUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) { + r0, _, _ := syscall.Syscall(procSetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} diff --git a/listener/tun/dev/wintun/dll_fromfile_windows.go b/listener/tun/dev/wintun/dll_fromfile_windows.go new file mode 100644 index 00000000..6cc2440c --- /dev/null +++ b/listener/tun/dev/wintun/dll_fromfile_windows.go @@ -0,0 +1,49 @@ +// +build !load_wintun_from_rsrc + +package wintun + +import ( + "fmt" + "sync" + "sync/atomic" + "unsafe" + + "golang.org/x/sys/windows" +) + +type lazyDLL struct { + Name string + mu sync.Mutex + module windows.Handle + onLoad func(d *lazyDLL) +} + +func (d *lazyDLL) Load() error { + if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil { + return nil + } + d.mu.Lock() + defer d.mu.Unlock() + if d.module != 0 { + return nil + } + + const ( + LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200 + LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 + ) + module, err := windows.LoadLibraryEx(d.Name, 0, LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32) + if err != nil { + return fmt.Errorf("Unable to load library: %w", err) + } + + atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module)) + if d.onLoad != nil { + d.onLoad(d) + } + return nil +} + +func (p *lazyProc) nameToAddr() (uintptr, error) { + return windows.GetProcAddress(p.dll.module, p.Name) +} diff --git a/listener/tun/dev/wintun/dll_fromrsrc_windows.go b/listener/tun/dev/wintun/dll_fromrsrc_windows.go new file mode 100644 index 00000000..c9f75489 --- /dev/null +++ b/listener/tun/dev/wintun/dll_fromrsrc_windows.go @@ -0,0 +1,56 @@ +// +build load_wintun_from_rsrc + +package wintun + +import ( + "fmt" + "sync" + "sync/atomic" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/Dreamacro/clash/listener/tun/dev/wintun/memmod" +) + +type lazyDLL struct { + Name string + mu sync.Mutex + module *memmod.Module + onLoad func(d *lazyDLL) +} + +func (d *lazyDLL) Load() error { + if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil { + return nil + } + d.mu.Lock() + defer d.mu.Unlock() + if d.module != nil { + return nil + } + + const ourModule windows.Handle = 0 + resInfo, err := windows.FindResource(ourModule, d.Name, windows.RT_RCDATA) + if err != nil { + return fmt.Errorf("Unable to find \"%v\" RCDATA resource: %w", d.Name, err) + } + data, err := windows.LoadResourceData(ourModule, resInfo) + if err != nil { + return fmt.Errorf("Unable to load resource: %w", err) + } + module, err := memmod.LoadLibrary(data) + if err != nil { + return fmt.Errorf("Unable to load library: %w", err) + } + + atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module)) + if d.onLoad != nil { + d.onLoad(d) + } + return nil +} + +func (p *lazyProc) nameToAddr() (uintptr, error) { + return p.dll.module.ProcAddressByName(p.Name) +} diff --git a/listener/tun/dev/wintun/dll_windows.go b/listener/tun/dev/wintun/dll_windows.go new file mode 100644 index 00000000..e15f28ac --- /dev/null +++ b/listener/tun/dev/wintun/dll_windows.go @@ -0,0 +1,54 @@ +package wintun + +import ( + "fmt" + "sync" + "sync/atomic" + "unsafe" +) + +func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL { + return &lazyDLL{Name: name, onLoad: onLoad} +} + +func (d *lazyDLL) NewProc(name string) *lazyProc { + return &lazyProc{dll: d, Name: name} +} + +type lazyProc struct { + Name string + mu sync.Mutex + dll *lazyDLL + addr uintptr +} + +func (p *lazyProc) Find() error { + if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil { + return nil + } + p.mu.Lock() + defer p.mu.Unlock() + if p.addr != 0 { + return nil + } + + err := p.dll.Load() + if err != nil { + return fmt.Errorf("Error loading %v DLL: %w", p.dll.Name, err) + } + addr, err := p.nameToAddr() + if err != nil { + return fmt.Errorf("Error getting %v address: %w", p.Name, err) + } + + atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr)) + return nil +} + +func (p *lazyProc) Addr() uintptr { + err := p.Find() + if err != nil { + panic(err) + } + return p.addr +} diff --git a/listener/tun/dev/wintun/memmod/memmod_windows.go b/listener/tun/dev/wintun/memmod/memmod_windows.go new file mode 100644 index 00000000..c75de5ad --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows.go @@ -0,0 +1,620 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +import ( + "errors" + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type addressList struct { + next *addressList + address uintptr +} + +func (head *addressList) free() { + for node := head; node != nil; node = node.next { + windows.VirtualFree(node.address, 0, windows.MEM_RELEASE) + } +} + +type Module struct { + headers *IMAGE_NT_HEADERS + codeBase uintptr + modules []windows.Handle + initialized bool + isDLL bool + isRelocated bool + nameExports map[string]uint16 + entry uintptr + blockedMemory *addressList +} + +func (module *Module) headerDirectory(idx int) *IMAGE_DATA_DIRECTORY { + return &module.headers.OptionalHeader.DataDirectory[idx] +} + +func (module *Module) copySections(address uintptr, size uintptr, old_headers *IMAGE_NT_HEADERS) error { + sections := module.headers.Sections() + for i := range sections { + if sections[i].SizeOfRawData == 0 { + // Section doesn't contain data in the dll itself, but may define uninitialized data. + sectionSize := old_headers.OptionalHeader.SectionAlignment + if sectionSize == 0 { + continue + } + dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress), + uintptr(sectionSize), + windows.MEM_COMMIT, + windows.PAGE_READWRITE) + if err != nil { + return fmt.Errorf("Error allocating section: %w", err) + } + + // Always use position from file to support alignments smaller than page size (allocation above will align to page size). + dest = module.codeBase + uintptr(sections[i].VirtualAddress) + // NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used. + sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff)) + var dst []byte + unsafeSlice(unsafe.Pointer(&dst), a2p(dest), int(sectionSize)) + for j := range dst { + dst[j] = 0 + } + continue + } + + if size < uintptr(sections[i].PointerToRawData+sections[i].SizeOfRawData) { + return errors.New("Incomplete section") + } + + // Commit memory block and copy data from dll. + dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress), + uintptr(sections[i].SizeOfRawData), + windows.MEM_COMMIT, + windows.PAGE_READWRITE) + if err != nil { + return fmt.Errorf("Error allocating memory block: %w", err) + } + + // Always use position from file to support alignments smaller than page size (allocation above will align to page size). + memcpy( + module.codeBase+uintptr(sections[i].VirtualAddress), + address+uintptr(sections[i].PointerToRawData), + uintptr(sections[i].SizeOfRawData)) + // NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used. + sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff)) + } + + return nil +} + +func (module *Module) realSectionSize(section *IMAGE_SECTION_HEADER) uintptr { + size := section.SizeOfRawData + if size != 0 { + return uintptr(size) + } + if (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) != 0 { + return uintptr(module.headers.OptionalHeader.SizeOfInitializedData) + } + if (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 { + return uintptr(module.headers.OptionalHeader.SizeOfUninitializedData) + } + return 0 +} + +type sectionFinalizeData struct { + address uintptr + alignedAddress uintptr + size uintptr + characteristics uint32 + last bool +} + +func (module *Module) finalizeSection(sectionData *sectionFinalizeData) error { + if sectionData.size == 0 { + return nil + } + + if (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0 { + // Section is not needed any more and can safely be freed. + if sectionData.address == sectionData.alignedAddress && + (sectionData.last || + (sectionData.size%uintptr(module.headers.OptionalHeader.SectionAlignment)) == 0) { + // Only allowed to decommit whole pages. + windows.VirtualFree(sectionData.address, sectionData.size, windows.MEM_DECOMMIT) + } + return nil + } + + // determine protection flags based on characteristics + var ProtectionFlags = [8]uint32{ + windows.PAGE_NOACCESS, // not writeable, not readable, not executable + windows.PAGE_EXECUTE, // not writeable, not readable, executable + windows.PAGE_READONLY, // not writeable, readable, not executable + windows.PAGE_EXECUTE_READ, // not writeable, readable, executable + windows.PAGE_WRITECOPY, // writeable, not readable, not executable + windows.PAGE_EXECUTE_WRITECOPY, // writeable, not readable, executable + windows.PAGE_READWRITE, // writeable, readable, not executable + windows.PAGE_EXECUTE_READWRITE, // writeable, readable, executable + } + protect := ProtectionFlags[sectionData.characteristics>>29] + if (sectionData.characteristics & IMAGE_SCN_MEM_NOT_CACHED) != 0 { + protect |= windows.PAGE_NOCACHE + } + + // Change memory access flags. + var oldProtect uint32 + err := windows.VirtualProtect(sectionData.address, sectionData.size, protect, &oldProtect) + if err != nil { + return fmt.Errorf("Error protecting memory page: %w", err) + } + + return nil +} + +func (module *Module) finalizeSections() error { + sections := module.headers.Sections() + imageOffset := module.headers.OptionalHeader.imageOffset() + sectionData := sectionFinalizeData{} + sectionData.address = uintptr(sections[0].PhysicalAddress()) | imageOffset + sectionData.alignedAddress = alignDown(sectionData.address, uintptr(module.headers.OptionalHeader.SectionAlignment)) + sectionData.size = module.realSectionSize(§ions[0]) + sectionData.characteristics = sections[0].Characteristics + + // Loop through all sections and change access flags. + for i := uint16(1); i < module.headers.FileHeader.NumberOfSections; i++ { + sectionAddress := uintptr(sections[i].PhysicalAddress()) | imageOffset + alignedAddress := alignDown(sectionAddress, uintptr(module.headers.OptionalHeader.SectionAlignment)) + sectionSize := module.realSectionSize(§ions[i]) + // Combine access flags of all sections that share a page. + // TODO: We currently share flags of a trailing large section with the page of a first small section. This should be optimized. + if sectionData.alignedAddress == alignedAddress || sectionData.address+sectionData.size > alignedAddress { + // Section shares page with previous. + if (sections[i].Characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 { + sectionData.characteristics = (sectionData.characteristics | sections[i].Characteristics) &^ IMAGE_SCN_MEM_DISCARDABLE + } else { + sectionData.characteristics |= sections[i].Characteristics + } + sectionData.size = sectionAddress + sectionSize - sectionData.address + continue + } + + err := module.finalizeSection(§ionData) + if err != nil { + return fmt.Errorf("Error finalizing section: %w", err) + } + sectionData.address = sectionAddress + sectionData.alignedAddress = alignedAddress + sectionData.size = sectionSize + sectionData.characteristics = sections[i].Characteristics + } + sectionData.last = true + err := module.finalizeSection(§ionData) + if err != nil { + return fmt.Errorf("Error finalizing section: %w", err) + } + return nil +} + +func (module *Module) executeTLS() { + directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_TLS) + if directory.VirtualAddress == 0 { + return + } + + tls := (*IMAGE_TLS_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) + callback := tls.AddressOfCallbacks + if callback != 0 { + for { + f := *(*uintptr)(a2p(callback)) + if f == 0 { + break + } + syscall.Syscall(f, 3, module.codeBase, uintptr(DLL_PROCESS_ATTACH), uintptr(0)) + callback += unsafe.Sizeof(f) + } + } +} + +func (module *Module) performBaseRelocation(delta uintptr) (relocated bool, err error) { + directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_BASERELOC) + if directory.Size == 0 { + return delta == 0, nil + } + + relocationHdr := (*IMAGE_BASE_RELOCATION)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) + for relocationHdr.VirtualAddress > 0 { + dest := module.codeBase + uintptr(relocationHdr.VirtualAddress) + + var relInfos []uint16 + unsafeSlice( + unsafe.Pointer(&relInfos), + a2p(uintptr(unsafe.Pointer(relocationHdr))+unsafe.Sizeof(*relocationHdr)), + int((uintptr(relocationHdr.SizeOfBlock)-unsafe.Sizeof(*relocationHdr))/unsafe.Sizeof(relInfos[0]))) + for _, relInfo := range relInfos { + // The upper 4 bits define the type of relocation. + relType := relInfo >> 12 + // The lower 12 bits define the offset. + relOffset := uintptr(relInfo & 0xfff) + + switch relType { + case IMAGE_REL_BASED_ABSOLUTE: + // Skip relocation. + + case IMAGE_REL_BASED_LOW: + *(*uint16)(a2p(dest + relOffset)) += uint16(delta & 0xffff) + break + + case IMAGE_REL_BASED_HIGH: + *(*uint16)(a2p(dest + relOffset)) += uint16(uint32(delta) >> 16) + break + + case IMAGE_REL_BASED_HIGHLOW: + *(*uint32)(a2p(dest + relOffset)) += uint32(delta) + + case IMAGE_REL_BASED_DIR64: + *(*uint64)(a2p(dest + relOffset)) += uint64(delta) + + case IMAGE_REL_BASED_THUMB_MOV32: + inst := *(*uint32)(a2p(dest + relOffset)) + imm16 := ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) + + ((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff) + if (inst & 0x8000fbf0) != 0x0000f240 { + return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVW", inst) + } + imm16 += uint32(delta) & 0xffff + hiDelta := (uint32(delta&0xffff0000) >> 16) + ((imm16 & 0xffff0000) >> 16) + *(*uint32)(a2p(dest + relOffset)) = (inst & 0x8f00fbf0) + ((imm16 >> 1) & 0x0400) + + ((imm16 >> 12) & 0x000f) + + ((imm16 << 20) & 0x70000000) + + ((imm16 << 16) & 0xff0000) + if hiDelta != 0 { + inst = *(*uint32)(a2p(dest + relOffset + 4)) + imm16 = ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) + + ((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff) + if (inst & 0x8000fbf0) != 0x0000f2c0 { + return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVT", inst) + } + imm16 += hiDelta + if imm16 > 0xffff { + return false, fmt.Errorf("Resulting immediate value won't fit: %08x", imm16) + } + *(*uint32)(a2p(dest + relOffset + 4)) = (inst & 0x8f00fbf0) + + ((imm16 >> 1) & 0x0400) + + ((imm16 >> 12) & 0x000f) + + ((imm16 << 20) & 0x70000000) + + ((imm16 << 16) & 0xff0000) + } + + default: + return false, fmt.Errorf("Unsupported relocation: %v", relType) + } + } + + // Advance to next relocation block. + relocationHdr = (*IMAGE_BASE_RELOCATION)(a2p(uintptr(unsafe.Pointer(relocationHdr)) + uintptr(relocationHdr.SizeOfBlock))) + } + return true, nil +} + +func (module *Module) buildImportTable() error { + directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT) + if directory.Size == 0 { + return nil + } + + module.modules = make([]windows.Handle, 0, 16) + importDesc := (*IMAGE_IMPORT_DESCRIPTOR)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) + for importDesc.Name != 0 { + handle, err := windows.LoadLibraryEx(windows.BytePtrToString((*byte)(a2p(module.codeBase+uintptr(importDesc.Name)))), 0, windows.LOAD_LIBRARY_SEARCH_SYSTEM32) + if err != nil { + return fmt.Errorf("Error loading module: %w", err) + } + var thunkRef, funcRef *uintptr + if importDesc.OriginalFirstThunk() != 0 { + thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.OriginalFirstThunk()))) + funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk))) + } else { + // No hint table. + thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk))) + funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk))) + } + for *thunkRef != 0 { + if IMAGE_SNAP_BY_ORDINAL(*thunkRef) { + *funcRef, err = windows.GetProcAddressByOrdinal(handle, IMAGE_ORDINAL(*thunkRef)) + } else { + thunkData := (*IMAGE_IMPORT_BY_NAME)(a2p(module.codeBase + *thunkRef)) + *funcRef, err = windows.GetProcAddress(handle, windows.BytePtrToString(&thunkData.Name[0])) + } + if err != nil { + windows.FreeLibrary(handle) + return fmt.Errorf("Error getting function address: %w", err) + } + thunkRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(thunkRef)) + unsafe.Sizeof(*thunkRef))) + funcRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(funcRef)) + unsafe.Sizeof(*funcRef))) + } + module.modules = append(module.modules, handle) + importDesc = (*IMAGE_IMPORT_DESCRIPTOR)(a2p(uintptr(unsafe.Pointer(importDesc)) + unsafe.Sizeof(*importDesc))) + } + return nil +} + +func (module *Module) buildNameExports() error { + directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) + if directory.Size == 0 { + return errors.New("No export table found") + } + exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) + if exports.NumberOfNames == 0 || exports.NumberOfFunctions == 0 { + return errors.New("No functions exported") + } + if exports.NumberOfNames == 0 { + return errors.New("No functions exported by name") + } + var nameRefs []uint32 + unsafeSlice(unsafe.Pointer(&nameRefs), a2p(module.codeBase+uintptr(exports.AddressOfNames)), int(exports.NumberOfNames)) + var ordinals []uint16 + unsafeSlice(unsafe.Pointer(&ordinals), a2p(module.codeBase+uintptr(exports.AddressOfNameOrdinals)), int(exports.NumberOfNames)) + module.nameExports = make(map[string]uint16) + for i := range nameRefs { + nameArray := windows.BytePtrToString((*byte)(a2p(module.codeBase + uintptr(nameRefs[i])))) + module.nameExports[nameArray] = ordinals[i] + } + return nil +} + +// LoadLibrary loads module image to memory. +func LoadLibrary(data []byte) (module *Module, err error) { + addr := uintptr(unsafe.Pointer(&data[0])) + size := uintptr(len(data)) + if size < unsafe.Sizeof(IMAGE_DOS_HEADER{}) { + return nil, errors.New("Incomplete IMAGE_DOS_HEADER") + } + dosHeader := (*IMAGE_DOS_HEADER)(a2p(addr)) + if dosHeader.E_magic != IMAGE_DOS_SIGNATURE { + return nil, fmt.Errorf("Not an MS-DOS binary (provided: %x, expected: %x)", dosHeader.E_magic, IMAGE_DOS_SIGNATURE) + } + if (size < uintptr(dosHeader.E_lfanew)+unsafe.Sizeof(IMAGE_NT_HEADERS{})) { + return nil, errors.New("Incomplete IMAGE_NT_HEADERS") + } + oldHeader := (*IMAGE_NT_HEADERS)(a2p(addr + uintptr(dosHeader.E_lfanew))) + if oldHeader.Signature != IMAGE_NT_SIGNATURE { + return nil, fmt.Errorf("Not an NT binary (provided: %x, expected: %x)", oldHeader.Signature, IMAGE_NT_SIGNATURE) + } + if oldHeader.FileHeader.Machine != imageFileProcess { + return nil, fmt.Errorf("Foreign platform (provided: %x, expected: %x)", oldHeader.FileHeader.Machine, imageFileProcess) + } + if (oldHeader.OptionalHeader.SectionAlignment & 1) != 0 { + return nil, errors.New("Unaligned section") + } + lastSectionEnd := uintptr(0) + sections := oldHeader.Sections() + optionalSectionSize := oldHeader.OptionalHeader.SectionAlignment + for i := range sections { + var endOfSection uintptr + if sections[i].SizeOfRawData == 0 { + // Section without data in the DLL + endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(optionalSectionSize) + } else { + endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(sections[i].SizeOfRawData) + } + if endOfSection > lastSectionEnd { + lastSectionEnd = endOfSection + } + } + alignedImageSize := alignUp(uintptr(oldHeader.OptionalHeader.SizeOfImage), uintptr(oldHeader.OptionalHeader.SectionAlignment)) + if alignedImageSize != alignUp(lastSectionEnd, uintptr(oldHeader.OptionalHeader.SectionAlignment)) { + return nil, errors.New("Section is not page-aligned") + } + + module = &Module{isDLL: (oldHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0} + defer func() { + if err != nil { + module.Free() + module = nil + } + }() + + // Reserve memory for image of library. + // TODO: Is it correct to commit the complete memory region at once? Calling DllEntry raises an exception if we don't. + module.codeBase, err = windows.VirtualAlloc(oldHeader.OptionalHeader.ImageBase, + alignedImageSize, + windows.MEM_RESERVE|windows.MEM_COMMIT, + windows.PAGE_READWRITE) + if err != nil { + // Try to allocate memory at arbitrary position. + module.codeBase, err = windows.VirtualAlloc(0, + alignedImageSize, + windows.MEM_RESERVE|windows.MEM_COMMIT, + windows.PAGE_READWRITE) + if err != nil { + err = fmt.Errorf("Error allocating code: %w", err) + return + } + } + err = module.check4GBBoundaries(alignedImageSize) + if err != nil { + err = fmt.Errorf("Error reallocating code: %w", err) + return + } + + if size < uintptr(oldHeader.OptionalHeader.SizeOfHeaders) { + err = errors.New("Incomplete headers") + return + } + // Commit memory for headers. + headers, err := windows.VirtualAlloc(module.codeBase, + uintptr(oldHeader.OptionalHeader.SizeOfHeaders), + windows.MEM_COMMIT, + windows.PAGE_READWRITE) + if err != nil { + err = fmt.Errorf("Error allocating headers: %w", err) + return + } + // Copy PE header to code. + memcpy(headers, addr, uintptr(oldHeader.OptionalHeader.SizeOfHeaders)) + module.headers = (*IMAGE_NT_HEADERS)(a2p(headers + uintptr(dosHeader.E_lfanew))) + + // Update position. + module.headers.OptionalHeader.ImageBase = module.codeBase + + // Copy sections from DLL file block to new memory location. + err = module.copySections(addr, size, oldHeader) + if err != nil { + err = fmt.Errorf("Error copying sections: %w", err) + return + } + + // Adjust base address of imported data. + locationDelta := module.headers.OptionalHeader.ImageBase - oldHeader.OptionalHeader.ImageBase + if locationDelta != 0 { + module.isRelocated, err = module.performBaseRelocation(locationDelta) + if err != nil { + err = fmt.Errorf("Error relocating module: %w", err) + return + } + } else { + module.isRelocated = true + } + + // Load required dlls and adjust function table of imports. + err = module.buildImportTable() + if err != nil { + err = fmt.Errorf("Error building import table: %w", err) + return + } + + // Mark memory pages depending on section headers and release sections that are marked as "discardable". + err = module.finalizeSections() + if err != nil { + err = fmt.Errorf("Error finalizing sections: %w", err) + return + } + + // TLS callbacks are executed BEFORE the main loading. + module.executeTLS() + + // Get entry point of loaded module. + if module.headers.OptionalHeader.AddressOfEntryPoint != 0 { + module.entry = module.codeBase + uintptr(module.headers.OptionalHeader.AddressOfEntryPoint) + if module.isDLL { + // Notify library about attaching to process. + r0, _, _ := syscall.Syscall(module.entry, 3, module.codeBase, uintptr(DLL_PROCESS_ATTACH), 0) + successful := r0 != 0 + if !successful { + err = windows.ERROR_DLL_INIT_FAILED + return + } + module.initialized = true + } + } + + module.buildNameExports() + return +} + +// Free releases module resources and unloads it. +func (module *Module) Free() { + if module.initialized { + // Notify library about detaching from process. + syscall.Syscall(module.entry, 3, module.codeBase, uintptr(DLL_PROCESS_DETACH), 0) + module.initialized = false + } + if module.modules != nil { + // Free previously opened libraries. + for _, handle := range module.modules { + windows.FreeLibrary(handle) + } + module.modules = nil + } + if module.codeBase != 0 { + windows.VirtualFree(module.codeBase, 0, windows.MEM_RELEASE) + module.codeBase = 0 + } + if module.blockedMemory != nil { + module.blockedMemory.free() + module.blockedMemory = nil + } +} + +// ProcAddressByName returns function address by exported name. +func (module *Module) ProcAddressByName(name string) (uintptr, error) { + directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) + if directory.Size == 0 { + return 0, errors.New("No export table found") + } + exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) + if module.nameExports == nil { + return 0, errors.New("No functions exported by name") + } + if idx, ok := module.nameExports[name]; ok { + if uint32(idx) > exports.NumberOfFunctions { + return 0, errors.New("Ordinal number too high") + } + // AddressOfFunctions contains the RVAs to the "real" functions. + return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil + } + return 0, errors.New("Function not found by name") +} + +// ProcAddressByOrdinal returns function address by exported ordinal. +func (module *Module) ProcAddressByOrdinal(ordinal uint16) (uintptr, error) { + directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) + if directory.Size == 0 { + return 0, errors.New("No export table found") + } + exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) + if uint32(ordinal) < exports.Base { + return 0, errors.New("Ordinal number too low") + } + idx := ordinal - uint16(exports.Base) + if uint32(idx) > exports.NumberOfFunctions { + return 0, errors.New("Ordinal number too high") + } + // AddressOfFunctions contains the RVAs to the "real" functions. + return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil +} + +func alignDown(value, alignment uintptr) uintptr { + return value & ^(alignment - 1) +} + +func alignUp(value, alignment uintptr) uintptr { + return (value + alignment - 1) & ^(alignment - 1) +} + +func a2p(addr uintptr) unsafe.Pointer { + return unsafe.Pointer(addr) +} + +func memcpy(dst, src, size uintptr) { + var d, s []byte + unsafeSlice(unsafe.Pointer(&d), a2p(dst), int(size)) + unsafeSlice(unsafe.Pointer(&s), a2p(src), int(size)) + copy(d, s) +} + +// unsafeSlice updates the slice slicePtr to be a slice +// referencing the provided data with its length & capacity set to +// lenCap. +// +// TODO: when Go 1.16 or Go 1.17 is the minimum supported version, +// update callers to use unsafe.Slice instead of this. +func unsafeSlice(slicePtr, data unsafe.Pointer, lenCap int) { + type sliceHeader struct { + Data unsafe.Pointer + Len int + Cap int + } + h := (*sliceHeader)(slicePtr) + h.Data = data + h.Len = lenCap + h.Cap = lenCap +} diff --git a/listener/tun/dev/wintun/memmod/memmod_windows_32.go b/listener/tun/dev/wintun/memmod/memmod_windows_32.go new file mode 100644 index 00000000..ac76bdcc --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows_32.go @@ -0,0 +1,16 @@ +// +build windows,386 windows,arm + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +func (opthdr *IMAGE_OPTIONAL_HEADER) imageOffset() uintptr { + return 0 +} + +func (module *Module) check4GBBoundaries(alignedImageSize uintptr) (err error) { + return +} diff --git a/listener/tun/dev/wintun/memmod/memmod_windows_386.go b/listener/tun/dev/wintun/memmod/memmod_windows_386.go new file mode 100644 index 00000000..475c5c52 --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows_386.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +const imageFileProcess = IMAGE_FILE_MACHINE_I386 diff --git a/listener/tun/dev/wintun/memmod/memmod_windows_64.go b/listener/tun/dev/wintun/memmod/memmod_windows_64.go new file mode 100644 index 00000000..a6203682 --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows_64.go @@ -0,0 +1,36 @@ +// +build windows,amd64 windows,arm64 + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +import ( + "fmt" + + "golang.org/x/sys/windows" +) + +func (opthdr *IMAGE_OPTIONAL_HEADER) imageOffset() uintptr { + return uintptr(opthdr.ImageBase & 0xffffffff00000000) +} + +func (module *Module) check4GBBoundaries(alignedImageSize uintptr) (err error) { + for (module.codeBase >> 32) < ((module.codeBase + alignedImageSize) >> 32) { + node := &addressList{ + next: module.blockedMemory, + address: module.codeBase, + } + module.blockedMemory = node + module.codeBase, err = windows.VirtualAlloc(0, + alignedImageSize, + windows.MEM_RESERVE|windows.MEM_COMMIT, + windows.PAGE_READWRITE) + if err != nil { + return fmt.Errorf("Error allocating memory block: %w", err) + } + } + return +} diff --git a/listener/tun/dev/wintun/memmod/memmod_windows_amd64.go b/listener/tun/dev/wintun/memmod/memmod_windows_amd64.go new file mode 100644 index 00000000..a021a633 --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows_amd64.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +const imageFileProcess = IMAGE_FILE_MACHINE_AMD64 diff --git a/listener/tun/dev/wintun/memmod/memmod_windows_arm.go b/listener/tun/dev/wintun/memmod/memmod_windows_arm.go new file mode 100644 index 00000000..4637a01d --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows_arm.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +const imageFileProcess = IMAGE_FILE_MACHINE_ARMNT diff --git a/listener/tun/dev/wintun/memmod/memmod_windows_arm64.go b/listener/tun/dev/wintun/memmod/memmod_windows_arm64.go new file mode 100644 index 00000000..b8f12596 --- /dev/null +++ b/listener/tun/dev/wintun/memmod/memmod_windows_arm64.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +const imageFileProcess = IMAGE_FILE_MACHINE_ARM64 diff --git a/listener/tun/dev/wintun/memmod/syscall_windows.go b/listener/tun/dev/wintun/memmod/syscall_windows.go new file mode 100644 index 00000000..31dd0b5d --- /dev/null +++ b/listener/tun/dev/wintun/memmod/syscall_windows.go @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +import "unsafe" + +const ( + IMAGE_DOS_SIGNATURE = 0x5A4D // MZ + IMAGE_OS2_SIGNATURE = 0x454E // NE + IMAGE_OS2_SIGNATURE_LE = 0x454C // LE + IMAGE_VXD_SIGNATURE = 0x454C // LE + IMAGE_NT_SIGNATURE = 0x00004550 // PE00 +) + +// DOS .EXE header +type IMAGE_DOS_HEADER struct { + E_magic uint16 // Magic number + E_cblp uint16 // Bytes on last page of file + E_cp uint16 // Pages in file + E_crlc uint16 // Relocations + E_cparhdr uint16 // Size of header in paragraphs + E_minalloc uint16 // Minimum extra paragraphs needed + E_maxalloc uint16 // Maximum extra paragraphs needed + E_ss uint16 // Initial (relative) SS value + E_sp uint16 // Initial SP value + E_csum uint16 // Checksum + E_ip uint16 // Initial IP value + E_cs uint16 // Initial (relative) CS value + E_lfarlc uint16 // File address of relocation table + E_ovno uint16 // Overlay number + E_res [4]uint16 // Reserved words + E_oemid uint16 // OEM identifier (for e_oeminfo) + E_oeminfo uint16 // OEM information; e_oemid specific + E_res2 [10]uint16 // Reserved words + E_lfanew int32 // File address of new exe header +} + +// File header format +type IMAGE_FILE_HEADER struct { + Machine uint16 + NumberOfSections uint16 + TimeDateStamp uint32 + PointerToSymbolTable uint32 + NumberOfSymbols uint32 + SizeOfOptionalHeader uint16 + Characteristics uint16 +} + +const ( + IMAGE_SIZEOF_FILE_HEADER = 20 + + IMAGE_FILE_RELOCS_STRIPPED = 0x0001 // Relocation info stripped from file. + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 // File is executable (i.e. no unresolved external references). + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 // Line nunbers stripped from file. + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 // Local symbols stripped from file. + IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 // Aggressively trim working set + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 // App can handle >2gb addresses + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 // Bytes of machine word are reversed. + IMAGE_FILE_32BIT_MACHINE = 0x0100 // 32 bit word machine. + IMAGE_FILE_DEBUG_STRIPPED = 0x0200 // Debugging info stripped from file in .DBG file + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400 // If Image is on removable media, copy and run from the swap file. + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800 // If Image is on Net, copy and run from the swap file. + IMAGE_FILE_SYSTEM = 0x1000 // System File. + IMAGE_FILE_DLL = 0x2000 // File is a DLL. + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000 // File should only be run on a UP machine + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 // Bytes of machine word are reversed. + + IMAGE_FILE_MACHINE_UNKNOWN = 0 + IMAGE_FILE_MACHINE_TARGET_HOST = 0x0001 // Useful for indicating we want to interact with the host and not a WoW guest. + IMAGE_FILE_MACHINE_I386 = 0x014c // Intel 386. + IMAGE_FILE_MACHINE_R3000 = 0x0162 // MIPS little-endian, 0x160 big-endian + IMAGE_FILE_MACHINE_R4000 = 0x0166 // MIPS little-endian + IMAGE_FILE_MACHINE_R10000 = 0x0168 // MIPS little-endian + IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x0169 // MIPS little-endian WCE v2 + IMAGE_FILE_MACHINE_ALPHA = 0x0184 // Alpha_AXP + IMAGE_FILE_MACHINE_SH3 = 0x01a2 // SH3 little-endian + IMAGE_FILE_MACHINE_SH3DSP = 0x01a3 + IMAGE_FILE_MACHINE_SH3E = 0x01a4 // SH3E little-endian + IMAGE_FILE_MACHINE_SH4 = 0x01a6 // SH4 little-endian + IMAGE_FILE_MACHINE_SH5 = 0x01a8 // SH5 + IMAGE_FILE_MACHINE_ARM = 0x01c0 // ARM Little-Endian + IMAGE_FILE_MACHINE_THUMB = 0x01c2 // ARM Thumb/Thumb-2 Little-Endian + IMAGE_FILE_MACHINE_ARMNT = 0x01c4 // ARM Thumb-2 Little-Endian + IMAGE_FILE_MACHINE_AM33 = 0x01d3 + IMAGE_FILE_MACHINE_POWERPC = 0x01F0 // IBM PowerPC Little-Endian + IMAGE_FILE_MACHINE_POWERPCFP = 0x01f1 + IMAGE_FILE_MACHINE_IA64 = 0x0200 // Intel 64 + IMAGE_FILE_MACHINE_MIPS16 = 0x0266 // MIPS + IMAGE_FILE_MACHINE_ALPHA64 = 0x0284 // ALPHA64 + IMAGE_FILE_MACHINE_MIPSFPU = 0x0366 // MIPS + IMAGE_FILE_MACHINE_MIPSFPU16 = 0x0466 // MIPS + IMAGE_FILE_MACHINE_AXP64 = IMAGE_FILE_MACHINE_ALPHA64 + IMAGE_FILE_MACHINE_TRICORE = 0x0520 // Infineon + IMAGE_FILE_MACHINE_CEF = 0x0CEF + IMAGE_FILE_MACHINE_EBC = 0x0EBC // EFI Byte Code + IMAGE_FILE_MACHINE_AMD64 = 0x8664 // AMD64 (K8) + IMAGE_FILE_MACHINE_M32R = 0x9041 // M32R little-endian + IMAGE_FILE_MACHINE_ARM64 = 0xAA64 // ARM64 Little-Endian + IMAGE_FILE_MACHINE_CEE = 0xC0EE +) + +// Directory format +type IMAGE_DATA_DIRECTORY struct { + VirtualAddress uint32 + Size uint32 +} + +const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 + +type IMAGE_NT_HEADERS struct { + Signature uint32 + FileHeader IMAGE_FILE_HEADER + OptionalHeader IMAGE_OPTIONAL_HEADER +} + +func (ntheader *IMAGE_NT_HEADERS) Sections() []IMAGE_SECTION_HEADER { + return (*[0xffff]IMAGE_SECTION_HEADER)(unsafe.Pointer( + (uintptr)(unsafe.Pointer(ntheader)) + + unsafe.Offsetof(ntheader.OptionalHeader) + + uintptr(ntheader.FileHeader.SizeOfOptionalHeader)))[:ntheader.FileHeader.NumberOfSections] +} + +const ( + IMAGE_DIRECTORY_ENTRY_EXPORT = 0 // Export Directory + IMAGE_DIRECTORY_ENTRY_IMPORT = 1 // Import Directory + IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 // Resource Directory + IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3 // Exception Directory + IMAGE_DIRECTORY_ENTRY_SECURITY = 4 // Security Directory + IMAGE_DIRECTORY_ENTRY_BASERELOC = 5 // Base Relocation Table + IMAGE_DIRECTORY_ENTRY_DEBUG = 6 // Debug Directory + IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7 // (X86 usage) + IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7 // Architecture Specific Data + IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8 // RVA of GP + IMAGE_DIRECTORY_ENTRY_TLS = 9 // TLS Directory + IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10 // Load Configuration Directory + IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11 // Bound Import Directory in headers + IMAGE_DIRECTORY_ENTRY_IAT = 12 // Import Address Table + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 // Delay Load Import Descriptors + IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 // COM Runtime descriptor +) + +const IMAGE_SIZEOF_SHORT_NAME = 8 + +// Section header format +type IMAGE_SECTION_HEADER struct { + Name [IMAGE_SIZEOF_SHORT_NAME]byte + physicalAddressOrVirtualSize uint32 + VirtualAddress uint32 + SizeOfRawData uint32 + PointerToRawData uint32 + PointerToRelocations uint32 + PointerToLinenumbers uint32 + NumberOfRelocations uint16 + NumberOfLinenumbers uint16 + Characteristics uint32 +} + +func (ishdr *IMAGE_SECTION_HEADER) PhysicalAddress() uint32 { + return ishdr.physicalAddressOrVirtualSize +} + +func (ishdr *IMAGE_SECTION_HEADER) SetPhysicalAddress(addr uint32) { + ishdr.physicalAddressOrVirtualSize = addr +} + +func (ishdr *IMAGE_SECTION_HEADER) VirtualSize() uint32 { + return ishdr.physicalAddressOrVirtualSize +} + +func (ishdr *IMAGE_SECTION_HEADER) SetVirtualSize(addr uint32) { + ishdr.physicalAddressOrVirtualSize = addr +} + +const ( + // Section characteristics. + IMAGE_SCN_TYPE_REG = 0x00000000 // Reserved. + IMAGE_SCN_TYPE_DSECT = 0x00000001 // Reserved. + IMAGE_SCN_TYPE_NOLOAD = 0x00000002 // Reserved. + IMAGE_SCN_TYPE_GROUP = 0x00000004 // Reserved. + IMAGE_SCN_TYPE_NO_PAD = 0x00000008 // Reserved. + IMAGE_SCN_TYPE_COPY = 0x00000010 // Reserved. + + IMAGE_SCN_CNT_CODE = 0x00000020 // Section contains code. + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 // Section contains initialized data. + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 // Section contains uninitialized data. + + IMAGE_SCN_LNK_OTHER = 0x00000100 // Reserved. + IMAGE_SCN_LNK_INFO = 0x00000200 // Section contains comments or some other type of information. + IMAGE_SCN_TYPE_OVER = 0x00000400 // Reserved. + IMAGE_SCN_LNK_REMOVE = 0x00000800 // Section contents will not become part of image. + IMAGE_SCN_LNK_COMDAT = 0x00001000 // Section contents comdat. + IMAGE_SCN_MEM_PROTECTED = 0x00004000 // Obsolete. + IMAGE_SCN_NO_DEFER_SPEC_EXC = 0x00004000 // Reset speculative exceptions handling bits in the TLB entries for this section. + IMAGE_SCN_GPREL = 0x00008000 // Section content can be accessed relative to GP + IMAGE_SCN_MEM_FARDATA = 0x00008000 + IMAGE_SCN_MEM_SYSHEAP = 0x00010000 // Obsolete. + IMAGE_SCN_MEM_PURGEABLE = 0x00020000 + IMAGE_SCN_MEM_16BIT = 0x00020000 + IMAGE_SCN_MEM_LOCKED = 0x00040000 + IMAGE_SCN_MEM_PRELOAD = 0x00080000 + + IMAGE_SCN_ALIGN_1BYTES = 0x00100000 // + IMAGE_SCN_ALIGN_2BYTES = 0x00200000 // + IMAGE_SCN_ALIGN_4BYTES = 0x00300000 // + IMAGE_SCN_ALIGN_8BYTES = 0x00400000 // + IMAGE_SCN_ALIGN_16BYTES = 0x00500000 // Default alignment if no others are specified. + IMAGE_SCN_ALIGN_32BYTES = 0x00600000 // + IMAGE_SCN_ALIGN_64BYTES = 0x00700000 // + IMAGE_SCN_ALIGN_128BYTES = 0x00800000 // + IMAGE_SCN_ALIGN_256BYTES = 0x00900000 // + IMAGE_SCN_ALIGN_512BYTES = 0x00A00000 // + IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000 // + IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000 // + IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000 // + IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000 // + IMAGE_SCN_ALIGN_MASK = 0x00F00000 + + IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000 // Section contains extended relocations. + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 // Section can be discarded. + IMAGE_SCN_MEM_NOT_CACHED = 0x04000000 // Section is not cachable. + IMAGE_SCN_MEM_NOT_PAGED = 0x08000000 // Section is not pageable. + IMAGE_SCN_MEM_SHARED = 0x10000000 // Section is shareable. + IMAGE_SCN_MEM_EXECUTE = 0x20000000 // Section is executable. + IMAGE_SCN_MEM_READ = 0x40000000 // Section is readable. + IMAGE_SCN_MEM_WRITE = 0x80000000 // Section is writeable. + + // TLS Characteristic Flags + IMAGE_SCN_SCALE_INDEX = 0x00000001 // Tls index is scaled. +) + +// Based relocation format +type IMAGE_BASE_RELOCATION struct { + VirtualAddress uint32 + SizeOfBlock uint32 +} + +const ( + IMAGE_REL_BASED_ABSOLUTE = 0 + IMAGE_REL_BASED_HIGH = 1 + IMAGE_REL_BASED_LOW = 2 + IMAGE_REL_BASED_HIGHLOW = 3 + IMAGE_REL_BASED_HIGHADJ = 4 + IMAGE_REL_BASED_MACHINE_SPECIFIC_5 = 5 + IMAGE_REL_BASED_RESERVED = 6 + IMAGE_REL_BASED_MACHINE_SPECIFIC_7 = 7 + IMAGE_REL_BASED_MACHINE_SPECIFIC_8 = 8 + IMAGE_REL_BASED_MACHINE_SPECIFIC_9 = 9 + IMAGE_REL_BASED_DIR64 = 10 + + IMAGE_REL_BASED_IA64_IMM64 = 9 + + IMAGE_REL_BASED_MIPS_JMPADDR = 5 + IMAGE_REL_BASED_MIPS_JMPADDR16 = 9 + + IMAGE_REL_BASED_ARM_MOV32 = 5 + IMAGE_REL_BASED_THUMB_MOV32 = 7 +) + +// Export Format +type IMAGE_EXPORT_DIRECTORY struct { + Characteristics uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + Name uint32 + Base uint32 + NumberOfFunctions uint32 + NumberOfNames uint32 + AddressOfFunctions uint32 // RVA from base of image + AddressOfNames uint32 // RVA from base of image + AddressOfNameOrdinals uint32 // RVA from base of image +} + +type IMAGE_IMPORT_BY_NAME struct { + Hint uint16 + Name [1]byte +} + +func IMAGE_ORDINAL(ordinal uintptr) uintptr { + return ordinal & 0xffff +} + +func IMAGE_SNAP_BY_ORDINAL(ordinal uintptr) bool { + return (ordinal & IMAGE_ORDINAL_FLAG) != 0 +} + +// Thread Local Storage +type IMAGE_TLS_DIRECTORY struct { + StartAddressOfRawData uintptr + EndAddressOfRawData uintptr + AddressOfIndex uintptr // PDWORD + AddressOfCallbacks uintptr // PIMAGE_TLS_CALLBACK *; + SizeOfZeroFill uint32 + Characteristics uint32 +} + +type IMAGE_IMPORT_DESCRIPTOR struct { + characteristicsOrOriginalFirstThunk uint32 // 0 for terminating null import descriptor + // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + TimeDateStamp uint32 // 0 if not bound, + // -1 if bound, and real date\time stamp + // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + // O.W. date/time stamp of DLL bound to (Old BIND) + ForwarderChain uint32 // -1 if no forwarders + Name uint32 + FirstThunk uint32 // RVA to IAT (if bound this IAT has actual addresses) +} + +func (imgimpdesc *IMAGE_IMPORT_DESCRIPTOR) Characteristics() uint32 { + return imgimpdesc.characteristicsOrOriginalFirstThunk +} + +func (imgimpdesc *IMAGE_IMPORT_DESCRIPTOR) OriginalFirstThunk() uint32 { + return imgimpdesc.characteristicsOrOriginalFirstThunk +} + +const ( + DLL_PROCESS_ATTACH = 1 + DLL_THREAD_ATTACH = 2 + DLL_THREAD_DETACH = 3 + DLL_PROCESS_DETACH = 0 +) + +type SYSTEM_INFO struct { + ProcessorArchitecture uint16 + Reserved uint16 + PageSize uint32 + MinimumApplicationAddress uintptr + MaximumApplicationAddress uintptr + ActiveProcessorMask uintptr + NumberOfProcessors uint32 + ProcessorType uint32 + AllocationGranularity uint32 + ProcessorLevel uint16 + ProcessorRevision uint16 +} diff --git a/listener/tun/dev/wintun/memmod/syscall_windows_32.go b/listener/tun/dev/wintun/memmod/syscall_windows_32.go new file mode 100644 index 00000000..a96d81ba --- /dev/null +++ b/listener/tun/dev/wintun/memmod/syscall_windows_32.go @@ -0,0 +1,45 @@ +// +build windows,386 windows,arm + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +// Optional header format +type IMAGE_OPTIONAL_HEADER struct { + Magic uint16 + MajorLinkerVersion uint8 + MinorLinkerVersion uint8 + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + BaseOfData uint32 + ImageBase uintptr + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uintptr + SizeOfStackCommit uintptr + SizeOfHeapReserve uintptr + SizeOfHeapCommit uintptr + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]IMAGE_DATA_DIRECTORY +} + +const IMAGE_ORDINAL_FLAG uintptr = 0x80000000 diff --git a/listener/tun/dev/wintun/memmod/syscall_windows_64.go b/listener/tun/dev/wintun/memmod/syscall_windows_64.go new file mode 100644 index 00000000..521262c8 --- /dev/null +++ b/listener/tun/dev/wintun/memmod/syscall_windows_64.go @@ -0,0 +1,44 @@ +// +build windows,amd64 windows,arm64 + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +package memmod + +// Optional header format +type IMAGE_OPTIONAL_HEADER struct { + Magic uint16 + MajorLinkerVersion uint8 + MinorLinkerVersion uint8 + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + ImageBase uintptr + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uintptr + SizeOfStackCommit uintptr + SizeOfHeapReserve uintptr + SizeOfHeapCommit uintptr + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]IMAGE_DATA_DIRECTORY +} + +const IMAGE_ORDINAL_FLAG uintptr = 0x8000000000000000 diff --git a/listener/tun/dev/wintun/session_windows.go b/listener/tun/dev/wintun/session_windows.go new file mode 100644 index 00000000..a801f53e --- /dev/null +++ b/listener/tun/dev/wintun/session_windows.go @@ -0,0 +1,103 @@ +package wintun + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type Session struct { + handle uintptr +} + +const ( + PacketSizeMax = 0xffff // Maximum packet size + RingCapacityMin = 0x20000 // Minimum ring capacity (128 kiB) + RingCapacityMax = 0x4000000 // Maximum ring capacity (64 MiB) +) + +// Packet with data +type Packet struct { + Next *Packet // Pointer to next packet in queue + Size uint32 // Size of packet (max WINTUN_MAX_IP_PACKET_SIZE) + Data *[PacketSizeMax]byte // Pointer to layer 3 IPv4 or IPv6 packet +} + +var ( + procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket") + procWintunEndSession = modwintun.NewProc("WintunEndSession") + procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent") + procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket") + procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket") + procWintunSendPacket = modwintun.NewProc("WintunSendPacket") + procWintunStartSession = modwintun.NewProc("WintunStartSession") +) + +func (wintun *Adapter) StartSession(capacity uint32) (session Session, err error) { + r0, _, e1 := syscall.Syscall(procWintunStartSession.Addr(), 2, uintptr(wintun.handle), uintptr(capacity), 0) + if r0 == 0 { + err = e1 + } else { + session = Session{r0} + } + return +} + +func (session Session) End() { + syscall.Syscall(procWintunEndSession.Addr(), 1, session.handle, 0, 0) + session.handle = 0 +} + +func (session Session) ReadWaitEvent() (handle windows.Handle) { + r0, _, _ := syscall.Syscall(procWintunGetReadWaitEvent.Addr(), 1, session.handle, 0, 0) + handle = windows.Handle(r0) + return +} + +func (session Session) ReceivePacket() (packet []byte, err error) { + var packetSize uint32 + r0, _, e1 := syscall.Syscall(procWintunReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packetSize)), 0) + if r0 == 0 { + err = e1 + return + } + unsafeSlice(unsafe.Pointer(&packet), unsafe.Pointer(r0), int(packetSize)) + return +} + +func (session Session) ReleaseReceivePacket(packet []byte) { + syscall.Syscall(procWintunReleaseReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0) +} + +func (session Session) AllocateSendPacket(packetSize int) (packet []byte, err error) { + r0, _, e1 := syscall.Syscall(procWintunAllocateSendPacket.Addr(), 2, session.handle, uintptr(packetSize), 0) + if r0 == 0 { + err = e1 + return + } + unsafeSlice(unsafe.Pointer(&packet), unsafe.Pointer(r0), int(packetSize)) + return +} + +func (session Session) SendPacket(packet []byte) { + syscall.Syscall(procWintunSendPacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0) +} + +// unsafeSlice updates the slice slicePtr to be a slice +// referencing the provided data with its length & capacity set to +// lenCap. +// +// TODO: when Go 1.16 or Go 1.17 is the minimum supported version, +// update callers to use unsafe.Slice instead of this. +func unsafeSlice(slicePtr, data unsafe.Pointer, lenCap int) { + type sliceHeader struct { + Data unsafe.Pointer + Len int + Cap int + } + h := (*sliceHeader)(slicePtr) + h.Data = data + h.Len = lenCap + h.Cap = lenCap +} diff --git a/listener/tun/dev/wintun/wintun_windows.go b/listener/tun/dev/wintun/wintun_windows.go new file mode 100644 index 00000000..edf78464 --- /dev/null +++ b/listener/tun/dev/wintun/wintun_windows.go @@ -0,0 +1,221 @@ +package wintun + +import ( + "errors" + "runtime" + "syscall" + "unsafe" + + "github.com/Dreamacro/clash/log" + "golang.org/x/sys/windows" +) + +type loggerLevel int + +const ( + logInfo loggerLevel = iota + logWarn + logErr +) + +const ( + PoolNameMax = 256 + AdapterNameMax = 128 +) + +type Pool [PoolNameMax]uint16 +type Adapter struct { + handle uintptr +} + +var ( + modwintun = newLazyDLL("wintun.dll", setupLogger) + + procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter") + procWintunDeleteAdapter = modwintun.NewProc("WintunDeleteAdapter") + procWintunDeletePoolDriver = modwintun.NewProc("WintunDeletePoolDriver") + procWintunEnumAdapters = modwintun.NewProc("WintunEnumAdapters") + procWintunFreeAdapter = modwintun.NewProc("WintunFreeAdapter") + procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter") + procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID") + procWintunGetAdapterName = modwintun.NewProc("WintunGetAdapterName") + procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion") + procWintunSetAdapterName = modwintun.NewProc("WintunSetAdapterName") +) + +func setupLogger(dll *lazyDLL) { + syscall.Syscall(dll.NewProc("WintunSetLogger").Addr(), 1, windows.NewCallback(func(level loggerLevel, msg *uint16) int { + var lv log.LogLevel + switch level { + case logInfo: + lv = log.INFO + case logWarn: + lv = log.WARNING + case logErr: + lv = log.ERROR + default: + lv = log.INFO + } + log.PrintLog(lv, "[Wintun] %s", windows.UTF16PtrToString(msg)) + return 0 + }), 0, 0) +} + +func MakePool(poolName string) (pool *Pool, err error) { + poolName16, err := windows.UTF16FromString(poolName) + if err != nil { + return + } + if len(poolName16) > PoolNameMax { + err = errors.New("Pool name too long") + return + } + pool = &Pool{} + copy(pool[:], poolName16) + return +} + +func (pool *Pool) String() string { + return windows.UTF16ToString(pool[:]) +} + +func freeAdapter(wintun *Adapter) { + syscall.Syscall(procWintunFreeAdapter.Addr(), 1, uintptr(wintun.handle), 0, 0) +} + +// OpenAdapter finds a Wintun adapter by its name. This function returns the adapter if found, or +// windows.ERROR_FILE_NOT_FOUND otherwise. If the adapter is found but not a Wintun-class or a +// member of the pool, this function returns windows.ERROR_ALREADY_EXISTS. The adapter must be +// released after use. +func (pool *Pool) OpenAdapter(ifname string) (wintun *Adapter, err error) { + ifname16, err := windows.UTF16PtrFromString(ifname) + if err != nil { + return nil, err + } + r0, _, e1 := syscall.Syscall(procWintunOpenAdapter.Addr(), 2, uintptr(unsafe.Pointer(pool)), uintptr(unsafe.Pointer(ifname16)), 0) + if r0 == 0 { + err = e1 + return + } + wintun = &Adapter{r0} + runtime.SetFinalizer(wintun, freeAdapter) + return +} + +// CreateAdapter creates a Wintun adapter. ifname is the requested name of the adapter, while +// requestedGUID is the GUID of the created network adapter, which then influences NLA generation +// deterministically. If it is set to nil, the GUID is chosen by the system at random, and hence a +// new NLA entry is created for each new adapter. It is called "requested" GUID because the API it +// uses is completely undocumented, and so there could be minor interesting complications with its +// usage. This function returns the network adapter ID and a flag if reboot is required. +func (pool *Pool) CreateAdapter(ifname string, requestedGUID *windows.GUID) (wintun *Adapter, rebootRequired bool, err error) { + var ifname16 *uint16 + ifname16, err = windows.UTF16PtrFromString(ifname) + if err != nil { + return + } + var _p0 uint32 + r0, _, e1 := syscall.Syscall6(procWintunCreateAdapter.Addr(), 4, uintptr(unsafe.Pointer(pool)), uintptr(unsafe.Pointer(ifname16)), uintptr(unsafe.Pointer(requestedGUID)), uintptr(unsafe.Pointer(&_p0)), 0, 0) + rebootRequired = _p0 != 0 + if r0 == 0 { + err = e1 + return + } + wintun = &Adapter{r0} + runtime.SetFinalizer(wintun, freeAdapter) + return +} + +// Delete deletes a Wintun adapter. This function succeeds if the adapter was not found. It returns +// a bool indicating whether a reboot is required. +func (wintun *Adapter) Delete(forceCloseSessions bool) (rebootRequired bool, err error) { + var _p0 uint32 + if forceCloseSessions { + _p0 = 1 + } + var _p1 uint32 + r1, _, e1 := syscall.Syscall(procWintunDeleteAdapter.Addr(), 3, uintptr(wintun.handle), uintptr(_p0), uintptr(unsafe.Pointer(&_p1))) + rebootRequired = _p1 != 0 + if r1 == 0 { + err = e1 + } + return +} + +// DeleteMatchingAdapters deletes all Wintun adapters, which match +// given criteria, and returns which ones it deleted, whether a reboot +// is required after, and which errors occurred during the process. +func (pool *Pool) DeleteMatchingAdapters(matches func(adapter *Adapter) bool, forceCloseSessions bool) (rebootRequired bool, errors []error) { + cb := func(handle uintptr, _ uintptr) int { + adapter := &Adapter{handle} + if !matches(adapter) { + return 1 + } + rebootRequired2, err := adapter.Delete(forceCloseSessions) + if err != nil { + errors = append(errors, err) + return 1 + } + rebootRequired = rebootRequired || rebootRequired2 + return 1 + } + r1, _, e1 := syscall.Syscall(procWintunEnumAdapters.Addr(), 3, uintptr(unsafe.Pointer(pool)), uintptr(windows.NewCallback(cb)), 0) + if r1 == 0 { + errors = append(errors, e1) + } + return +} + +// Name returns the name of the Wintun adapter. +func (wintun *Adapter) Name() (ifname string, err error) { + var ifname16 [AdapterNameMax]uint16 + r1, _, e1 := syscall.Syscall(procWintunGetAdapterName.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&ifname16[0])), 0) + if r1 == 0 { + err = e1 + return + } + ifname = windows.UTF16ToString(ifname16[:]) + return +} + +// DeleteDriver deletes all Wintun adapters in a pool and if there are no more adapters in any other +// pools, also removes Wintun from the driver store, usually called by uninstallers. +func (pool *Pool) DeleteDriver() (rebootRequired bool, err error) { + var _p0 uint32 + r1, _, e1 := syscall.Syscall(procWintunDeletePoolDriver.Addr(), 2, uintptr(unsafe.Pointer(pool)), uintptr(unsafe.Pointer(&_p0)), 0) + rebootRequired = _p0 != 0 + if r1 == 0 { + err = e1 + } + return + +} + +// SetName sets name of the Wintun adapter. +func (wintun *Adapter) SetName(ifname string) (err error) { + ifname16, err := windows.UTF16FromString(ifname) + if err != nil { + return err + } + r1, _, e1 := syscall.Syscall(procWintunSetAdapterName.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&ifname16[0])), 0) + if r1 == 0 { + err = e1 + } + return +} + +// RunningVersion returns the version of the running Wintun driver. +func RunningVersion() (version uint32, err error) { + r0, _, e1 := syscall.Syscall(procWintunGetRunningDriverVersion.Addr(), 0, 0, 0, 0) + version = uint32(r0) + if version == 0 { + err = e1 + } + return +} + +// LUID returns the LUID of the adapter. +func (wintun *Adapter) LUID() (luid uint64) { + syscall.Syscall(procWintunGetAdapterLUID.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&luid)), 0) + return +} diff --git a/listener/tun/ipstack/gvisor/tun.go b/listener/tun/ipstack/gvisor/tun.go new file mode 100644 index 00000000..a0ad15c1 --- /dev/null +++ b/listener/tun/ipstack/gvisor/tun.go @@ -0,0 +1,263 @@ +package gvisor + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "strings" + "sync" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/component/resolver" + "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/dns" + "github.com/Dreamacro/clash/listener/tun/dev" + "github.com/Dreamacro/clash/listener/tun/ipstack" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/socks5" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" + "gvisor.dev/gvisor/pkg/waiter" +) + +const nicID tcpip.NICID = 1 + +type gvisorAdapter struct { + device dev.TunDevice + ipstack *stack.Stack + dnsserver *DNSServer + udpIn chan<- *inbound.PacketAdapter + + stackName string + autoRoute bool + linkCache *channel.Endpoint + wg sync.WaitGroup // wait for goroutines to stop + + writeHandle *channel.NotificationHandle +} + +// GvisorAdapter create GvisorAdapter +func NewAdapter(device dev.TunDevice, conf config.Tun, tunAddress string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) { + + ipstack := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, + TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol}, + }) + + adapter := &gvisorAdapter{ + device: device, + ipstack: ipstack, + udpIn: udpIn, + stackName: conf.Stack, + autoRoute: conf.AutoRoute, + } + + linkEP, err := adapter.AsLinkEndpoint() + if err != nil { + return nil, fmt.Errorf("unable to create virtual endpoint: %v", err) + } + + if err := ipstack.CreateNIC(nicID, linkEP); err != nil { + return nil, fmt.Errorf("fail to create NIC in ipstack: %v", err) + } + + ipstack.SetPromiscuousMode(nicID, true) // Accept all the traffice from this NIC + ipstack.SetSpoofing(nicID, true) // Otherwise our TCP connection can not find the route backward + + // Add route for ipv4 & ipv6 + // So FindRoute will return correct route to tun NIC + subnet, _ := tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 4)), tcpip.AddressMask(strings.Repeat("\x00", 4))) + ipstack.AddRoute(tcpip.Route{Destination: subnet, Gateway: "", NIC: nicID}) + subnet, _ = tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 6)), tcpip.AddressMask(strings.Repeat("\x00", 6))) + ipstack.AddRoute(tcpip.Route{Destination: subnet, Gateway: "", NIC: nicID}) + + // TCP handler + // maximum number of half-open tcp connection set to 1024 + // receive buffer size set to 20k + tcpFwd := tcp.NewForwarder(ipstack, 20*1024, 1024, func(r *tcp.ForwarderRequest) { + var wq waiter.Queue + ep, err := r.CreateEndpoint(&wq) + if err != nil { + log.Warnln("Can't create TCP Endpoint in ipstack: %v", err) + r.Complete(true) + return + } + r.Complete(false) + + conn := gonet.NewTCPConn(&wq, ep) + + // if the endpoint is not in connected state, conn.RemoteAddr() will return nil + // this protection may be not enough, but will help us debug the panic + if conn.RemoteAddr() == nil { + log.Warnln("TCP endpoint is not connected, current state: %v", tcp.EndpointState(ep.State())) + conn.Close() + return + } + + target := getAddr(ep.Info().(*stack.TransportEndpointInfo).ID) + tcpIn <- inbound.NewSocket(target, conn, C.TUN) + }) + ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpFwd.HandlePacket) + + // UDP handler + ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, adapter.udpHandlePacket) + + if resolver.DefaultResolver != nil { + err = adapter.ReCreateDNSServer(resolver.DefaultResolver.(*dns.Resolver), resolver.DefaultHostMapper.(*dns.ResolverEnhancer), conf.DNSListen) + if err != nil { + return nil, err + } + } + + return adapter, nil +} + +func (t *gvisorAdapter) Stack() string { + return t.stackName +} + +func (t *gvisorAdapter) AutoRoute() bool { + return t.autoRoute +} + +// Close close the TunAdapter +func (t *gvisorAdapter) Close() { + if t.dnsserver != nil { + t.dnsserver.Stop() + } + if t.ipstack != nil { + t.ipstack.Close() + } + if t.device != nil { + _ = t.device.Close() + } +} + +func (t *gvisorAdapter) udpHandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { + // ref: gvisor pkg/tcpip/transport/udp/endpoint.go HandlePacket + hdr := header.UDP(pkt.TransportHeader().View()) + if int(hdr.Length()) > pkt.Data().Size()+header.UDPMinimumSize { + // Malformed packet. + t.ipstack.Stats().UDP.MalformedPacketsReceived.Increment() + return true + } + + target := getAddr(id) + + packet := &fakeConn{ + id: id, + pkt: pkt, + s: t.ipstack, + payload: pkt.Data().AsRange().ToOwnedView(), + } + + select { + case t.udpIn <- inbound.NewPacket(target, packet, C.TUN): + default: + } + + return true +} + +// Wait wait goroutines to exit +func (t *gvisorAdapter) Wait() { + t.wg.Wait() +} + +func (t *gvisorAdapter) AsLinkEndpoint() (result stack.LinkEndpoint, err error) { + if t.linkCache != nil { + return t.linkCache, nil + } + + mtu, err := t.device.MTU() + + if err != nil { + return nil, errors.New("unable to get device mtu") + } + + linkEP := channel.New(512, uint32(mtu), "") + + // start Read loop. read ip packet from tun and write it to ipstack + t.wg.Add(1) + go func() { + for !t.device.IsClose() { + packet := make([]byte, mtu) + n, err := t.device.Read(packet) + if err != nil && !t.device.IsClose() { + log.Errorln("can not read from tun: %v", err) + } + var p tcpip.NetworkProtocolNumber + switch header.IPVersion(packet) { + case header.IPv4Version: + p = header.IPv4ProtocolNumber + case header.IPv6Version: + p = header.IPv6ProtocolNumber + } + if linkEP.IsAttached() { + linkEP.InjectInbound(p, stack.NewPacketBuffer(stack.PacketBufferOptions{ + Data: buffer.View(packet[:n]).ToVectorisedView(), + })) + } else { + log.Debugln("received packet from tun when %s is not attached to any dispatcher.", t.device.Name()) + } + } + t.wg.Done() + t.Close() + log.Debugln("%v stop read loop", t.device.Name()) + }() + + // start write notification + t.writeHandle = linkEP.AddNotify(t) + t.linkCache = linkEP + return t.linkCache, nil +} + +// WriteNotify implements channel.Notification.WriteNotify. +func (t *gvisorAdapter) WriteNotify() { + packet, ok := t.linkCache.Read() + if ok { + var vv buffer.VectorisedView + // Append upper headers. + vv.AppendView(packet.Pkt.NetworkHeader().View()) + vv.AppendView(packet.Pkt.TransportHeader().View()) + // Append data payload. + vv.Append(packet.Pkt.Data().ExtractVV()) + + _, err := t.device.Write(vv.ToView()) + if err != nil && !t.device.IsClose() { + log.Errorln("can not write to tun: %v", err) + } + } +} + +func getAddr(id stack.TransportEndpointID) socks5.Addr { + ipv4 := id.LocalAddress.To4() + + // get the big-endian binary represent of port + port := make([]byte, 2) + binary.BigEndian.PutUint16(port, id.LocalPort) + + if ipv4 != "" { + addr := make([]byte, 1+net.IPv4len+2) + addr[0] = socks5.AtypIPv4 + copy(addr[1:1+net.IPv4len], []byte(ipv4)) + addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] + return addr + } else { + addr := make([]byte, 1+net.IPv6len+2) + addr[0] = socks5.AtypIPv6 + copy(addr[1:1+net.IPv6len], []byte(id.LocalAddress)) + addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1] + return addr + } +} diff --git a/listener/tun/ipstack/gvisor/tundns.go b/listener/tun/ipstack/gvisor/tundns.go new file mode 100644 index 00000000..920e2019 --- /dev/null +++ b/listener/tun/ipstack/gvisor/tundns.go @@ -0,0 +1,280 @@ +package gvisor + +import ( + "fmt" + "net" + + "github.com/Dreamacro/clash/dns" + "github.com/Dreamacro/clash/log" + D "github.com/miekg/dns" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" + "gvisor.dev/gvisor/pkg/tcpip/ports" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" +) + +var ( + ipv4Zero = tcpip.Address(net.IPv4zero.To4()) + ipv6Zero = tcpip.Address(net.IPv6zero.To16()) +) + +// DNSServer is DNS Server listening on tun devcice +type DNSServer struct { + *dns.Server + resolver *dns.Resolver + + stack *stack.Stack + tcpListener net.Listener + udpEndpoint *dnsEndpoint + udpEndpointID *stack.TransportEndpointID + tcpip.NICID +} + +// dnsEndpoint is a TransportEndpoint that will register to stack +type dnsEndpoint struct { + stack.TransportEndpoint + stack *stack.Stack + uniqueID uint64 + server *dns.Server +} + +// Keep track of the source of DNS request +type dnsResponseWriter struct { + s *stack.Stack + pkt *stack.PacketBuffer // The request packet + id stack.TransportEndpointID +} + +func (e *dnsEndpoint) UniqueID() uint64 { + return e.uniqueID +} + +func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) { + hdr := header.UDP(pkt.TransportHeader().View()) + if int(hdr.Length()) > pkt.Data().Size()+header.UDPMinimumSize { + // Malformed packet. + e.stack.Stats().UDP.MalformedPacketsReceived.Increment() + return + } + + // server DNS + var msg D.Msg + msg.Unpack(pkt.Data().AsRange().ToOwnedView()) + writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id} + go e.server.ServeDNS(&writer, &msg) +} + +func (e *dnsEndpoint) Close() { +} + +func (e *dnsEndpoint) Wait() { + +} + +func (e *dnsEndpoint) HandleError(transErr stack.TransportError, pkt *stack.PacketBuffer) { + log.Warnln("DNS endpoint get a transport error: %v", transErr) + log.Debugln("DNS endpoint transport error packet : %v", pkt) +} + +// Abort implements stack.TransportEndpoint.Abort. +func (e *dnsEndpoint) Abort() { + e.Close() +} + +func (w *dnsResponseWriter) LocalAddr() net.Addr { + return &net.UDPAddr{IP: net.IP(w.id.LocalAddress), Port: int(w.id.LocalPort)} +} + +func (w *dnsResponseWriter) RemoteAddr() net.Addr { + return &net.UDPAddr{IP: net.IP(w.id.RemoteAddress), Port: int(w.id.RemotePort)} +} + +func (w *dnsResponseWriter) WriteMsg(msg *D.Msg) error { + b, err := msg.Pack() + if err != nil { + return err + } + _, err = w.Write(b) + return err +} +func (w *dnsResponseWriter) TsigStatus() error { + // Unsupported + return nil +} +func (w *dnsResponseWriter) TsigTimersOnly(bool) { + // Unsupported +} +func (w *dnsResponseWriter) Hijack() { + // Unsupported +} + +func (w *dnsResponseWriter) Write(b []byte) (int, error) { + v := buffer.NewView(len(b)) + copy(v, b) + data := v.ToVectorisedView() + // w.id.LocalAddress is the source ip of DNS response + r, _ := w.s.FindRoute(w.pkt.NICID, w.id.LocalAddress, w.id.RemoteAddress, w.pkt.NetworkProtocolNumber, false /* multicastLoop */) + return writeUDP(r, data, w.id.LocalPort, w.id.RemotePort) +} + +func (w *dnsResponseWriter) Close() error { + return nil +} + +// CreateDNSServer create a dns server on given netstack +func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, ip net.IP, port int, nicID tcpip.NICID) (*DNSServer, error) { + + var v4 bool + var err error + + address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)} + if ip.To4() != nil { + v4 = true + address.Addr = tcpip.Address(ip.To4()) + // netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints + s.AddAddress(nicID, ipv4.ProtocolNumber, address.Addr) + } else { + v4 = false + address.Addr = tcpip.Address(ip.To16()) + s.AddAddress(nicID, ipv6.ProtocolNumber, address.Addr) + } + if address.Addr == ipv4Zero || address.Addr == ipv6Zero { + address.Addr = "" + } + + handler := dns.NewHandler(resolver, mapper) + serverIn := &dns.Server{} + serverIn.SetHandler(handler) + + // UDP DNS + id := &stack.TransportEndpointID{ + LocalAddress: address.Addr, + LocalPort: uint16(port), + RemotePort: 0, + RemoteAddress: "", + } + + // TransportEndpoint for DNS + endpoint := &dnsEndpoint{ + stack: s, + uniqueID: s.UniqueID(), + server: serverIn, + } + + if tcpiperr := s.RegisterTransportEndpoint( + []tcpip.NetworkProtocolNumber{ + ipv4.ProtocolNumber, + ipv6.ProtocolNumber, + }, + udp.ProtocolNumber, + *id, + endpoint, + ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect. + nicID); err != nil { + log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String()) + } + + // TCP DNS + var tcpListener net.Listener + if v4 { + tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber) + } else { + tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber) + } + if err != nil { + return nil, fmt.Errorf("can not listen on tun: %v", err) + } + + server := &DNSServer{ + Server: serverIn, + resolver: resolver, + stack: s, + tcpListener: tcpListener, + udpEndpoint: endpoint, + udpEndpointID: id, + NICID: nicID, + } + server.SetHandler(handler) + server.Server.Server = &D.Server{Listener: tcpListener, Handler: server} + + go func() { + server.ActivateAndServe() + }() + + return server, err +} + +// Stop stop the DNS Server on tun +func (s *DNSServer) Stop() { + // shutdown TCP DNS Server + s.Server.Shutdown() + // remove TCP endpoint from stack + if s.Listener != nil { + s.Listener.Close() + } + // remove udp endpoint from stack + s.stack.UnregisterTransportEndpoint( + []tcpip.NetworkProtocolNumber{ + ipv4.ProtocolNumber, + ipv6.ProtocolNumber, + }, + udp.ProtocolNumber, + *s.udpEndpointID, + s.udpEndpoint, + ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint + s.NICID) +} + +// DNSListen return the listening address of DNS Server +func (t *gvisorAdapter) DNSListen() string { + if t.dnsserver != nil { + id := t.dnsserver.udpEndpointID + return fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort) + } + return "" +} + +// Stop stop the DNS Server on tun +func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addr string) error { + if addr == "" && t.dnsserver == nil { + return nil + } + + if addr == t.DNSListen() && t.dnsserver != nil && t.dnsserver.resolver == resolver { + return nil + } + + if t.dnsserver != nil { + t.dnsserver.Stop() + t.dnsserver = nil + log.Debugln("tun DNS server stoped") + } + + var err error + _, port, err := net.SplitHostPort(addr) + if port == "0" || port == "" || err != nil { + return nil + } + + if resolver == nil { + return fmt.Errorf("failed to create DNS server on tun: resolver not provided") + } + + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return err + } + + server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID) + if err != nil { + return err + } + t.dnsserver = server + log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled()) + return nil +} diff --git a/listener/tun/ipstack/gvisor/utils.go b/listener/tun/ipstack/gvisor/utils.go new file mode 100644 index 00000000..31213894 --- /dev/null +++ b/listener/tun/ipstack/gvisor/utils.go @@ -0,0 +1,109 @@ +package gvisor + +import ( + "fmt" + "net" + + "github.com/Dreamacro/clash/component/resolver" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" +) + +type fakeConn struct { + id stack.TransportEndpointID // The endpoint of incomming packet, it's remote address is the source address it sent from + pkt *stack.PacketBuffer // The original packet comming from tun + s *stack.Stack + payload []byte + fakeip *bool +} + +func (c *fakeConn) Data() []byte { + return c.payload +} + +func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { + v := buffer.View(b) + data := v.ToVectorisedView() + + var localAddress tcpip.Address + var localPort uint16 + // if addr is not provided, write back use original dst Addr as src Addr + if c.FakeIP() || addr == nil { + localAddress = c.id.LocalAddress + localPort = c.id.LocalPort + } else { + udpaddr, _ := addr.(*net.UDPAddr) + localAddress = tcpip.Address(udpaddr.IP) + localPort = uint16(udpaddr.Port) + } + + r, _ := c.s.FindRoute(c.pkt.NICID, localAddress, c.id.RemoteAddress, c.pkt.NetworkProtocolNumber, false /* multicastLoop */) + return writeUDP(r, data, localPort, c.id.RemotePort) +} + +func (c *fakeConn) LocalAddr() net.Addr { + return &net.UDPAddr{IP: net.IP(c.id.RemoteAddress), Port: int(c.id.RemotePort)} +} + +func (c *fakeConn) Close() error { + return nil +} + +func (c *fakeConn) Drop() { + +} + +func (c *fakeConn) FakeIP() bool { + if c.fakeip != nil { + return *c.fakeip + } + fakeip := resolver.IsFakeIP(net.IP(c.id.LocalAddress.To4())) + c.fakeip = &fakeip + return fakeip +} + +func writeUDP(r *stack.Route, data buffer.VectorisedView, localPort, remotePort uint16) (int, error) { + const protocol = udp.ProtocolNumber + // Allocate a buffer for the UDP header. + + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + ReserveHeaderBytes: header.UDPMinimumSize + int(r.MaxHeaderLength()), + Data: data, + }) + + // Initialize the header. + udp := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize)) + + length := uint16(pkt.Size()) + udp.Encode(&header.UDPFields{ + SrcPort: localPort, + DstPort: remotePort, + Length: length, + }) + + // Set the checksum field unless TX checksum offload is enabled. + // On IPv4, UDP checksum is optional, and a zero value indicates the + // transmitter skipped the checksum generation (RFC768). + // On IPv6, UDP checksum is not optional (RFC2460 Section 8.1). + if r.RequiresTXTransportChecksum() { + xsum := r.PseudoHeaderChecksum(protocol, length) + for _, v := range data.Views() { + xsum = header.Checksum(v, xsum) + } + udp.SetChecksum(^udp.CalculateChecksum(xsum)) + } + + ttl := r.DefaultTTL() + + if err := r.WritePacket(stack.NetworkHeaderParams{Protocol: protocol, TTL: ttl, TOS: 0 /* default */}, pkt); err != nil { + r.Stats().UDP.PacketSendErrors.Increment() + return 0, fmt.Errorf("%v", err) + } + + // Track count of packets sent. + r.Stats().UDP.PacketsSent.Increment() + return data.Size(), nil +} diff --git a/listener/tun/ipstack/stack_adapter.go b/listener/tun/ipstack/stack_adapter.go new file mode 100644 index 00000000..333c3ca5 --- /dev/null +++ b/listener/tun/ipstack/stack_adapter.go @@ -0,0 +1,9 @@ +package ipstack + +// TunAdapter hold the state of tun/tap interface +type TunAdapter interface { + Close() + Stack() string + DNSListen() string + AutoRoute() bool +} diff --git a/listener/tun/ipstack/system/dns.go b/listener/tun/ipstack/system/dns.go new file mode 100644 index 00000000..21a22393 --- /dev/null +++ b/listener/tun/ipstack/system/dns.go @@ -0,0 +1,101 @@ +package system + +import ( + "encoding/binary" + "io" + "net" + "time" + + "github.com/Dreamacro/clash/component/resolver" + + D "github.com/miekg/dns" + + "github.com/kr328/tun2socket/binding" + "github.com/kr328/tun2socket/redirect" +) + +const defaultDnsReadTimeout = time.Second * 30 + +func shouldHijackDns(dnsAddr binding.Address, targetAddr binding.Address) bool { + if targetAddr.Port != 53 { + return false + } + + return dnsAddr.IP.Equal(net.IPv4zero) || dnsAddr.IP.Equal(targetAddr.IP) +} + +func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) { + go func() { + answer, err := relayDnsPacket(pkt) + + if err != nil { + return + } + + _ = sender(answer, &binding.Endpoint{ + Source: ep.Target, + Target: ep.Source, + }) + }() +} + +func hijackTCPDns(conn net.Conn) { + go func() { + defer conn.Close() + + for { + if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil { + return + } + + var length uint16 + if binary.Read(conn, binary.BigEndian, &length) != nil { + return + } + + data := make([]byte, length) + + _, err := io.ReadFull(conn, data) + if err != nil { + return + } + + rb, err := relayDnsPacket(data) + if err != nil { + continue + } + + if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil { + return + } + + if _, err := conn.Write(rb); err != nil { + return + } + } + }() +} + +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 { + return nil, err + } + + for _, ans := range r.Answer { + header := ans.Header() + + if header.Class == D.ClassINET && (header.Rrtype == D.TypeA || header.Rrtype == D.TypeAAAA) { + header.Ttl = 1 + } + } + + r.SetRcode(msg, r.Rcode) + r.Compress = true + return r.Pack() +} diff --git a/listener/tun/ipstack/system/log.go b/listener/tun/ipstack/system/log.go new file mode 100644 index 00000000..6546dc27 --- /dev/null +++ b/listener/tun/ipstack/system/log.go @@ -0,0 +1,21 @@ +package system + +import "github.com/Dreamacro/clash/log" + +type logger struct{} + +func (l *logger) D(format string, args ...interface{}) { + log.Debugln("[TUN] "+format, args...) +} + +func (l *logger) I(format string, args ...interface{}) { + log.Infoln("[TUN] "+format, args...) +} + +func (l *logger) W(format string, args ...interface{}) { + log.Warnln("[TUN] "+format, args...) +} + +func (l *logger) E(format string, args ...interface{}) { + log.Errorln("[TUN] "+format, args...) +} diff --git a/listener/tun/ipstack/system/tcp.go b/listener/tun/ipstack/system/tcp.go new file mode 100644 index 00000000..07b99eac --- /dev/null +++ b/listener/tun/ipstack/system/tcp.go @@ -0,0 +1,37 @@ +package system + +import ( + "net" + "strconv" + + "github.com/kr328/tun2socket/binding" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" +) + +func handleTCP(conn net.Conn, endpoint *binding.Endpoint, tcpIn chan<- C.ConnContext) { + src := &net.TCPAddr{ + IP: endpoint.Source.IP, + Port: int(endpoint.Source.Port), + Zone: "", + } + dst := &net.TCPAddr{ + IP: endpoint.Target.IP, + Port: int(endpoint.Target.Port), + Zone: "", + } + + metadata := &C.Metadata{ + NetWork: C.TCP, + Type: C.TUN, + SrcIP: src.IP, + DstIP: dst.IP, + SrcPort: strconv.Itoa(src.Port), + DstPort: strconv.Itoa(dst.Port), + AddrType: C.AtypIPv4, + Host: "", + } + + tcpIn <- context.NewConnContext(conn, metadata) +} diff --git a/listener/tun/ipstack/system/tun.go b/listener/tun/ipstack/system/tun.go new file mode 100644 index 00000000..354d9ed1 --- /dev/null +++ b/listener/tun/ipstack/system/tun.go @@ -0,0 +1,125 @@ +package system + +import ( + "net" + "strconv" + "sync" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/tun/dev" + "github.com/Dreamacro/clash/listener/tun/ipstack" + "github.com/Dreamacro/clash/log" + "github.com/kr328/tun2socket" + "github.com/kr328/tun2socket/binding" + "github.com/kr328/tun2socket/redirect" +) + +type systemAdapter struct { + device dev.TunDevice + tun *tun2socket.Tun2Socket + lock sync.Mutex + stackName string + dnsListen string + autoRoute bool +} + +func NewAdapter(device dev.TunDevice, conf config.Tun, mtu int, gateway, mirror string, onStop func(), tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) { + + adapter := &systemAdapter{ + device: device, + stackName: conf.Stack, + dnsListen: conf.DNSListen, + autoRoute: conf.AutoRoute, + } + + adapter.lock.Lock() + defer adapter.lock.Unlock() + + //adapter.stopLocked() + + dnsHost, dnsPort, err := net.SplitHostPort(conf.DNSListen) + if err != nil { + return nil, err + } + + dnsP, err := strconv.Atoi(dnsPort) + if err != nil { + return nil, err + } + + dnsAddr := binding.Address{ + IP: net.ParseIP(dnsHost), + Port: uint16(dnsP), + } + + t := tun2socket.NewTun2Socket(device, mtu, net.ParseIP(gateway), net.ParseIP(mirror)) + + t.SetAllocator(allocUDP) + t.SetClosedHandler(onStop) + t.SetLogger(&logger{}) + + t.SetTCPHandler(func(conn net.Conn, endpoint *binding.Endpoint) { + if shouldHijackDns(dnsAddr, endpoint.Target) { + hijackTCPDns(conn) + + if log.Level() == log.DEBUG { + log.Debugln("[TUN] hijack dns tcp: %s:%d", endpoint.Target.IP.String(), endpoint.Target.Port) + } + return + } + + handleTCP(conn, endpoint, tcpIn) + }) + t.SetUDPHandler(func(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) { + if shouldHijackDns(dnsAddr, endpoint.Target) { + hijackUDPDns(payload, endpoint, sender) + + if log.Level() == log.DEBUG { + log.Debugln("[TUN] hijack dns udp: %s:%d", endpoint.Target.IP.String(), endpoint.Target.Port) + } + return + } + + handleUDP(payload, endpoint, sender, udpIn) + }) + + t.Start() + + adapter.tun = t + + return adapter, nil +} + +func (t *systemAdapter) Stack() string { + return t.stackName +} + +func (t *systemAdapter) AutoRoute() bool { + return t.autoRoute +} + +func (t *systemAdapter) DNSListen() string { + return t.dnsListen +} + +func (t *systemAdapter) Close() { + t.lock.Lock() + defer t.lock.Unlock() + + t.stopLocked() +} + +func (t *systemAdapter) stopLocked() { + if t.tun != nil { + t.tun.Close() + } + + if t.device != nil { + _ = t.device.Close() + } + + t.tun = nil + t.device = nil +} diff --git a/listener/tun/ipstack/system/udp.go b/listener/tun/ipstack/system/udp.go new file mode 100644 index 00000000..afb99174 --- /dev/null +++ b/listener/tun/ipstack/system/udp.go @@ -0,0 +1,74 @@ +package system + +import ( + "io" + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" + "github.com/kr328/tun2socket/binding" + "github.com/kr328/tun2socket/redirect" +) + +type udpPacket struct { + source binding.Address + data []byte + send redirect.UDPSender +} + +func (u *udpPacket) Data() []byte { + return u.data +} + +func (u *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) { + uAddr, ok := addr.(*net.UDPAddr) + if !ok { + return 0, io.ErrClosedPipe + } + + return len(b), u.send(b, &binding.Endpoint{ + Source: binding.Address{IP: uAddr.IP, Port: uint16(uAddr.Port)}, + Target: u.source, + }) +} + +func (u *udpPacket) Drop() { + recycleUDP(u.data) +} + +func (u *udpPacket) LocalAddr() net.Addr { + return &net.UDPAddr{ + IP: u.source.IP, + Port: int(u.source.Port), + Zone: "", + } +} + +func handleUDP(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender, udpIn chan<- *inbound.PacketAdapter) { + pkt := &udpPacket{ + source: endpoint.Source, + data: payload, + send: sender, + } + + rAddr := &net.UDPAddr{ + IP: endpoint.Target.IP, + Port: int(endpoint.Target.Port), + Zone: "", + } + + select { + case udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.TUN): + default: + } +} + +func allocUDP(size int) []byte { + return pool.Get(size) +} + +func recycleUDP(payload []byte) { + _ = pool.Put(payload) +} diff --git a/listener/tun/tun_adapter.go b/listener/tun/tun_adapter.go new file mode 100644 index 00000000..64395328 --- /dev/null +++ b/listener/tun/tun_adapter.go @@ -0,0 +1,51 @@ +package tun + +import ( + "errors" + "fmt" + "strings" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/tun/dev" + "github.com/Dreamacro/clash/listener/tun/ipstack" + "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor" + "github.com/Dreamacro/clash/listener/tun/ipstack/system" + "github.com/Dreamacro/clash/log" +) + +// New create TunAdapter +func New(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) { + tunAddress := "198.18.0.1" + autoRoute := conf.AutoRoute + stack := conf.Stack + var tunAdapter ipstack.TunAdapter + + device, err := dev.OpenTunDevice(tunAddress, autoRoute) + if err != nil { + return nil, fmt.Errorf("can't open tun: %v", err) + } + + mtu, err := device.MTU() + if err != nil { + _ = device.Close() + return nil, errors.New("unable to get device mtu") + } + + if strings.EqualFold(stack, "system") { + tunAdapter, err = system.NewAdapter(device, conf, mtu, tunAddress, tunAddress, func() {}, tcpIn, udpIn) + } else if strings.EqualFold(stack, "gvisor") { + tunAdapter, err = gvisor.NewAdapter(device, conf, tunAddress, tcpIn, udpIn) + } else { + err = fmt.Errorf("can not support tun ip stack: %s, only support \"system\" and \"gvisor\"", stack) + } + + if err != nil { + _ = device.Close() + return nil, err + } + + log.Infoln("Tun adapter listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", device.Name(), tunAddress, mtu, autoRoute, stack) + return tunAdapter, nil +} diff --git a/log/log.go b/log/log.go index 73ff0b9e..f5c57768 100644 --- a/log/log.go +++ b/log/log.go @@ -97,3 +97,9 @@ func newLog(logLevel LogLevel, format string, v ...interface{}) *Event { Payload: fmt.Sprintf(format, v...), } } + +func PrintLog(logLevel LogLevel, format string, v ...interface{}) { + event := newLog(logLevel, format, v...) + logCh <- event + print(event) +} diff --git a/main.go b/main.go index 4a7dff0b..79b48804 100644 --- a/main.go +++ b/main.go @@ -98,6 +98,12 @@ func main() { } sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) <-sigCh + + // clean up + log.Warnln("Clash clean up") + hub.CleanUp() + + log.Warnln("Clash shutting down") } diff --git a/rule/base.go b/rule/base.go index 0f2d9f29..17238547 100644 --- a/rule/base.go +++ b/rule/base.go @@ -2,6 +2,8 @@ package rules import ( "errors" + + C "github.com/Dreamacro/clash/constant" ) var ( @@ -18,3 +20,14 @@ func HasNoResolve(params []string) bool { } return false } + +func findNetwork(params []string) C.NetWork { + for _, p := range params { + if p == "tcp" { + return C.TCP + } else if p == "udp" { + return C.UDP + } + } + return C.ALLNet +} diff --git a/rule/domain.go b/rule/domain.go index f23ab18d..ae4c46d7 100644 --- a/rule/domain.go +++ b/rule/domain.go @@ -9,6 +9,7 @@ import ( type Domain struct { domain string adapter string + network C.NetWork } func (d *Domain) RuleType() C.RuleType { @@ -34,9 +35,14 @@ func (d *Domain) ShouldResolveIP() bool { return false } -func NewDomain(domain string, adapter string) *Domain { +func (d *Domain) NetWork() C.NetWork { + return d.network +} + +func NewDomain(domain string, adapter string, network C.NetWork) *Domain { return &Domain{ domain: strings.ToLower(domain), adapter: adapter, + network: network, } } diff --git a/rule/domain_keyword.go b/rule/domain_keyword.go index b3d6fbaa..3d55758c 100644 --- a/rule/domain_keyword.go +++ b/rule/domain_keyword.go @@ -9,6 +9,7 @@ import ( type DomainKeyword struct { keyword string adapter string + network C.NetWork } func (dk *DomainKeyword) RuleType() C.RuleType { @@ -35,9 +36,14 @@ func (dk *DomainKeyword) ShouldResolveIP() bool { return false } -func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { +func (dk *DomainKeyword) NetWork() C.NetWork { + return dk.network +} + +func NewDomainKeyword(keyword string, adapter string, network C.NetWork) *DomainKeyword { return &DomainKeyword{ keyword: strings.ToLower(keyword), adapter: adapter, + network: network, } } diff --git a/rule/domain_suffix.go b/rule/domain_suffix.go index a1f9f28f..1d1116bd 100644 --- a/rule/domain_suffix.go +++ b/rule/domain_suffix.go @@ -9,6 +9,7 @@ import ( type DomainSuffix struct { suffix string adapter string + network C.NetWork } func (ds *DomainSuffix) RuleType() C.RuleType { @@ -35,9 +36,14 @@ func (ds *DomainSuffix) ShouldResolveIP() bool { return false } -func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { +func (ds *DomainSuffix) NetWork() C.NetWork { + return ds.network +} + +func NewDomainSuffix(suffix string, adapter string, network C.NetWork) *DomainSuffix { return &DomainSuffix{ suffix: strings.ToLower(suffix), adapter: adapter, + network: network, } } diff --git a/rule/final.go b/rule/final.go index 91c0b9d9..ffcd0ca8 100644 --- a/rule/final.go +++ b/rule/final.go @@ -28,6 +28,10 @@ func (f *Match) ShouldResolveIP() bool { return false } +func (f *Match) NetWork() C.NetWork { + return C.ALLNet +} + func NewMatch(adapter string) *Match { return &Match{ adapter: adapter, diff --git a/rule/geodata/attr.go b/rule/geodata/attr.go new file mode 100644 index 00000000..a171c7d8 --- /dev/null +++ b/rule/geodata/attr.go @@ -0,0 +1,51 @@ +package geodata + +import ( + "strings" + + "github.com/Dreamacro/clash/rule/geodata/router" +) + +type AttributeList struct { + matcher []AttributeMatcher +} + +func (al *AttributeList) Match(domain *router.Domain) bool { + for _, matcher := range al.matcher { + if !matcher.Match(domain) { + return false + } + } + return true +} + +func (al *AttributeList) IsEmpty() bool { + return len(al.matcher) == 0 +} + +func parseAttrs(attrs []string) *AttributeList { + al := new(AttributeList) + for _, attr := range attrs { + trimmedAttr := strings.ToLower(strings.TrimSpace(attr)) + if len(trimmedAttr) == 0 { + continue + } + al.matcher = append(al.matcher, BooleanMatcher(trimmedAttr)) + } + return al +} + +type AttributeMatcher interface { + Match(*router.Domain) bool +} + +type BooleanMatcher string + +func (m BooleanMatcher) Match(domain *router.Domain) bool { + for _, attr := range domain.Attribute { + if strings.EqualFold(attr.GetKey(), string(m)) { + return true + } + } + return false +} diff --git a/rule/geodata/geodata.go b/rule/geodata/geodata.go new file mode 100644 index 00000000..07eac40c --- /dev/null +++ b/rule/geodata/geodata.go @@ -0,0 +1,86 @@ +package geodata + +import ( + "errors" + "fmt" + "strings" + + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/rule/geodata/router" +) + +type loader struct { + LoaderImplementation +} + +func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) { + return l.LoadGeoSiteWithAttr("geosite.dat", list) +} + +func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { + parts := strings.Split(siteWithAttr, "@") + if len(parts) == 0 { + return nil, errors.New("empty rule") + } + list := strings.TrimSpace(parts[0]) + attrVal := parts[1:] + + if len(list) == 0 { + return nil, fmt.Errorf("empty listname in rule: %s", siteWithAttr) + } + + domains, err := l.LoadSite(file, list) + if err != nil { + return nil, err + } + + attrs := parseAttrs(attrVal) + if attrs.IsEmpty() { + if strings.Contains(siteWithAttr, "@") { + log.Warnln("empty attribute list: %s", siteWithAttr) + } + return domains, nil + } + + filteredDomains := make([]*router.Domain, 0, len(domains)) + hasAttrMatched := false + for _, domain := range domains { + if attrs.Match(domain) { + hasAttrMatched = true + filteredDomains = append(filteredDomains, domain) + } + } + if !hasAttrMatched { + log.Warnln("attribute match no rule: geosite: %s", siteWithAttr) + } + + return filteredDomains, nil +} + +func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) { + return l.LoadIP("geoip.dat", country) +} + +var loaders map[string]func() LoaderImplementation + +func RegisterGeoDataLoaderImplementationCreator(name string, loader func() LoaderImplementation) { + if loaders == nil { + loaders = map[string]func() LoaderImplementation{} + } + loaders[name] = loader +} + +func getGeoDataLoaderImplementation(name string) (LoaderImplementation, error) { + if geoLoader, ok := loaders[name]; ok { + return geoLoader(), nil + } + return nil, fmt.Errorf("unable to locate GeoData loader %s", name) +} + +func GetGeoDataLoader(name string) (Loader, error) { + loadImpl, err := getGeoDataLoaderImplementation(name) + if err == nil { + return &loader{loadImpl}, nil + } + return nil, err +} diff --git a/rule/geodata/geodataproto.go b/rule/geodata/geodataproto.go new file mode 100644 index 00000000..20745a71 --- /dev/null +++ b/rule/geodata/geodataproto.go @@ -0,0 +1,15 @@ +package geodata + +import "github.com/Dreamacro/clash/rule/geodata/router" + +type LoaderImplementation interface { + LoadSite(filename, list string) ([]*router.Domain, error) + LoadIP(filename, country string) ([]*router.CIDR, error) +} + +type Loader interface { + LoaderImplementation + LoadGeoSite(list string) ([]*router.Domain, error) + LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) + LoadGeoIP(country string) ([]*router.CIDR, error) +} diff --git a/rule/geodata/memconservative/cache.go b/rule/geodata/memconservative/cache.go new file mode 100644 index 00000000..3a1ac7e6 --- /dev/null +++ b/rule/geodata/memconservative/cache.go @@ -0,0 +1,142 @@ +package memconservative + +import ( + "fmt" + "io/ioutil" + "strings" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/rule/geodata/router" + "google.golang.org/protobuf/proto" +) + +type GeoIPCache map[string]*router.GeoIP + +func (g GeoIPCache) Has(key string) bool { + return !(g.Get(key) == nil) +} + +func (g GeoIPCache) Get(key string) *router.GeoIP { + if g == nil { + return nil + } + return g[key] +} + +func (g GeoIPCache) Set(key string, value *router.GeoIP) { + if g == nil { + g = make(map[string]*router.GeoIP) + } + g[key] = value +} + +func (g GeoIPCache) Unmarshal(filename, code string) (*router.GeoIP, error) { + asset := C.Path.GetAssetLocation(filename) + idx := strings.ToLower(asset + ":" + code) + if g.Has(idx) { + return g.Get(idx), nil + } + + geoipBytes, err := Decode(asset, code) + switch err { + case nil: + var geoip router.GeoIP + if err := proto.Unmarshal(geoipBytes, &geoip); err != nil { + return nil, err + } + g.Set(idx, &geoip) + return &geoip, nil + + case errCodeNotFound: + return nil, fmt.Errorf("country code %s%s%s", code, " not found in ", filename) + + case errFailedToReadBytes, errFailedToReadExpectedLenBytes, + errInvalidGeodataFile, errInvalidGeodataVarintLength: + log.Warnln("failed to decode geoip file: %s%s", filename, ", fallback to the original ReadFile method") + geoipBytes, err = ioutil.ReadFile(asset) + if err != nil { + return nil, err + } + var geoipList router.GeoIPList + if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { + return nil, err + } + for _, geoip := range geoipList.GetEntry() { + if strings.EqualFold(code, geoip.GetCountryCode()) { + g.Set(idx, geoip) + return geoip, nil + } + } + + default: + return nil, err + } + + return nil, fmt.Errorf("country code %s%s%s", code, " not found in ", filename) +} + +type GeoSiteCache map[string]*router.GeoSite + +func (g GeoSiteCache) Has(key string) bool { + return !(g.Get(key) == nil) +} + +func (g GeoSiteCache) Get(key string) *router.GeoSite { + if g == nil { + return nil + } + return g[key] +} + +func (g GeoSiteCache) Set(key string, value *router.GeoSite) { + if g == nil { + g = make(map[string]*router.GeoSite) + } + g[key] = value +} + +func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error) { + asset := C.Path.GetAssetLocation(filename) + idx := strings.ToLower(asset + ":" + code) + if g.Has(idx) { + return g.Get(idx), nil + } + + geositeBytes, err := Decode(asset, code) + switch err { + case nil: + var geosite router.GeoSite + if err := proto.Unmarshal(geositeBytes, &geosite); err != nil { + return nil, err + } + g.Set(idx, &geosite) + return &geosite, nil + + case errCodeNotFound: + return nil, fmt.Errorf("list %s%s%s", code, " not found in ", filename) + + case errFailedToReadBytes, errFailedToReadExpectedLenBytes, + errInvalidGeodataFile, errInvalidGeodataVarintLength: + log.Warnln("failed to decode geoip file: %s%s", filename, ", fallback to the original ReadFile method") + geositeBytes, err = ioutil.ReadFile(asset) + if err != nil { + return nil, err + } + var geositeList router.GeoSiteList + if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { + return nil, err + } + for _, geosite := range geositeList.GetEntry() { + if strings.EqualFold(code, geosite.GetCountryCode()) { + g.Set(idx, geosite) + return geosite, nil + } + } + + default: + return nil, err + } + + return nil, fmt.Errorf("list %s%s%s", code, " not found in ", filename) +} diff --git a/rule/geodata/memconservative/decode.go b/rule/geodata/memconservative/decode.go new file mode 100644 index 00000000..321043e8 --- /dev/null +++ b/rule/geodata/memconservative/decode.go @@ -0,0 +1,105 @@ +package memconservative + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + + "google.golang.org/protobuf/encoding/protowire" +) + +var ( + errFailedToReadBytes = errors.New("failed to read bytes") + errFailedToReadExpectedLenBytes = errors.New("failed to read expected length of bytes") + errInvalidGeodataFile = errors.New("invalid geodata file") + errInvalidGeodataVarintLength = errors.New("invalid geodata varint length") + errCodeNotFound = errors.New("code not found") +) + +func emitBytes(f io.ReadSeeker, code string) ([]byte, error) { + count := 1 + isInner := false + tempContainer := make([]byte, 0, 5) + + var result []byte + var advancedN uint64 = 1 + var geoDataVarintLength, codeVarintLength, varintLenByteLen uint64 = 0, 0, 0 + +Loop: + for { + container := make([]byte, advancedN) + bytesRead, err := f.Read(container) + if err == io.EOF { + return nil, errCodeNotFound + } + if err != nil { + return nil, errFailedToReadBytes + } + if bytesRead != len(container) { + return nil, errFailedToReadExpectedLenBytes + } + + switch count { + case 1, 3: // data type ((field_number << 3) | wire_type) + if container[0] != 10 { // byte `0A` equals to `10` in decimal + return nil, errInvalidGeodataFile + } + advancedN = 1 + count++ + case 2, 4: // data length + tempContainer = append(tempContainer, container...) + if container[0] > 127 { // max one-byte-length byte `7F`(0FFF FFFF) equals to `127` in decimal + advancedN = 1 + goto Loop + } + lenVarint, n := protowire.ConsumeVarint(tempContainer) + if n < 0 { + return nil, errInvalidGeodataVarintLength + } + tempContainer = nil + if !isInner { + isInner = true + geoDataVarintLength = lenVarint + advancedN = 1 + } else { + isInner = false + codeVarintLength = lenVarint + varintLenByteLen = uint64(n) + advancedN = codeVarintLength + } + count++ + case 5: // data value + if strings.EqualFold(string(container), code) { + count++ + offset := -(1 + int64(varintLenByteLen) + int64(codeVarintLength)) + f.Seek(offset, 1) // back to the start of GeoIP or GeoSite varint + advancedN = geoDataVarintLength // the number of bytes to be read in next round + } else { + count = 1 + offset := int64(geoDataVarintLength) - int64(codeVarintLength) - int64(varintLenByteLen) - 1 + f.Seek(offset, 1) // skip the unmatched GeoIP or GeoSite varint + advancedN = 1 // the next round will be the start of another GeoIPList or GeoSiteList + } + case 6: // matched GeoIP or GeoSite varint + result = container + break Loop + } + } + return result, nil +} + +func Decode(filename, code string) ([]byte, error) { + f, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error()) + } + defer f.Close() + + geoBytes, err := emitBytes(f, code) + if err != nil { + return nil, err + } + return geoBytes, nil +} diff --git a/rule/geodata/memconservative/memc.go b/rule/geodata/memconservative/memc.go new file mode 100644 index 00000000..d0cbfa7f --- /dev/null +++ b/rule/geodata/memconservative/memc.go @@ -0,0 +1,40 @@ +package memconservative + +import ( + "fmt" + "runtime" + + "github.com/Dreamacro/clash/rule/geodata" + "github.com/Dreamacro/clash/rule/geodata/router" +) + +type memConservativeLoader struct { + geoipcache GeoIPCache + geositecache GeoSiteCache +} + +func (m *memConservativeLoader) LoadIP(filename, country string) ([]*router.CIDR, error) { + defer runtime.GC() + geoip, err := m.geoipcache.Unmarshal(filename, country) + if err != nil { + return nil, fmt.Errorf("failed to decode geodata file: %s, base error: %s", filename, err.Error()) + } + return geoip.Cidr, nil +} + +func (m *memConservativeLoader) LoadSite(filename, list string) ([]*router.Domain, error) { + defer runtime.GC() + geosite, err := m.geositecache.Unmarshal(filename, list) + if err != nil { + return nil, fmt.Errorf("failed to decode geodata file: %s, base error: %s", filename, err.Error()) + } + return geosite.Domain, nil +} + +func newMemConservativeLoader() geodata.LoaderImplementation { + return &memConservativeLoader{make(map[string]*router.GeoIP), make(map[string]*router.GeoSite)} +} + +func init() { + geodata.RegisterGeoDataLoaderImplementationCreator("memconservative", newMemConservativeLoader) +} diff --git a/rule/geodata/router/condition.go b/rule/geodata/router/condition.go new file mode 100644 index 00000000..cfe7cf42 --- /dev/null +++ b/rule/geodata/router/condition.go @@ -0,0 +1,112 @@ +package router + +import ( + "fmt" + "net" + "strings" + + "github.com/Dreamacro/clash/rule/geodata/strmatcher" +) + +var matcherTypeMap = map[Domain_Type]strmatcher.Type{ + Domain_Plain: strmatcher.Substr, + Domain_Regex: strmatcher.Regex, + Domain_Domain: strmatcher.Domain, + Domain_Full: strmatcher.Full, +} + +func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) { + matcherType, f := matcherTypeMap[domain.Type] + if !f { + return nil, fmt.Errorf("unsupported domain type %v", domain.Type) + } + + matcher, err := matcherType.New(domain.Value) + if err != nil { + return nil, fmt.Errorf("failed to create domain matcher, base error: %s", err.Error()) + } + + return matcher, nil +} + +type DomainMatcher struct { + matchers strmatcher.IndexMatcher +} + +func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) { + g := strmatcher.NewMphMatcherGroup() + for _, d := range domains { + matcherType, f := matcherTypeMap[d.Type] + if !f { + return nil, fmt.Errorf("unsupported domain type %v", d.Type) + } + _, err := g.AddPattern(d.Value, matcherType) + if err != nil { + return nil, err + } + } + g.Build() + return &DomainMatcher{ + matchers: g, + }, nil +} + +func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) { + g := new(strmatcher.MatcherGroup) + for _, d := range domains { + m, err := domainToMatcher(d) + if err != nil { + return nil, err + } + g.Add(m) + } + + return &DomainMatcher{ + matchers: g, + }, nil +} + +func (m *DomainMatcher) ApplyDomain(domain string) bool { + return len(m.matchers.Match(strings.ToLower(domain))) > 0 +} + +type MultiGeoIPMatcher struct { + matchers []*GeoIPMatcher +} + +func NewMultiGeoIPMatcher(geoips []*GeoIP) (*MultiGeoIPMatcher, error) { + var matchers []*GeoIPMatcher + for _, geoip := range geoips { + matcher, err := globalGeoIPContainer.Add(geoip) + if err != nil { + return nil, err + } + matchers = append(matchers, matcher) + } + + matcher := &MultiGeoIPMatcher{ + matchers: matchers, + } + + return matcher, nil +} + +func (m *MultiGeoIPMatcher) ApplyIp(ip net.IP) bool { + + for _, matcher := range m.matchers { + if matcher.Match(ip) { + return true + } + } + + return false +} + +func NewGeoIPMatcher(geoip *GeoIP) (*GeoIPMatcher, error) { + matcher, err := globalGeoIPContainer.Add(geoip) + if err != nil { + return nil, err + } + + return matcher, nil +} diff --git a/rule/geodata/router/condition_geoip.go b/rule/geodata/router/condition_geoip.go new file mode 100644 index 00000000..5a4bb5ca --- /dev/null +++ b/rule/geodata/router/condition_geoip.go @@ -0,0 +1,243 @@ +package router + +import ( + "encoding/binary" + "fmt" + "net" + "sort" +) + +// CIDRList is an alias of []*CIDR to provide sort.Interface. +type CIDRList []*CIDR + +// Len implements sort.Interface. +func (l *CIDRList) Len() int { + return len(*l) +} + +// Less implements sort.Interface. +func (l *CIDRList) Less(i int, j int) bool { + ci := (*l)[i] + cj := (*l)[j] + + if len(ci.Ip) < len(cj.Ip) { + return true + } + + if len(ci.Ip) > len(cj.Ip) { + return false + } + + for k := 0; k < len(ci.Ip); k++ { + if ci.Ip[k] < cj.Ip[k] { + return true + } + + if ci.Ip[k] > cj.Ip[k] { + return false + } + } + + return ci.Prefix < cj.Prefix +} + +// Swap implements sort.Interface. +func (l *CIDRList) Swap(i int, j int) { + (*l)[i], (*l)[j] = (*l)[j], (*l)[i] +} + +type ipv6 struct { + a uint64 + b uint64 +} + +type GeoIPMatcher struct { + countryCode string + reverseMatch bool + ip4 []uint32 + prefix4 []uint8 + ip6 []ipv6 + prefix6 []uint8 +} + +func normalize4(ip uint32, prefix uint8) uint32 { + return (ip >> (32 - prefix)) << (32 - prefix) +} + +func normalize6(ip ipv6, prefix uint8) ipv6 { + if prefix <= 64 { + ip.a = (ip.a >> (64 - prefix)) << (64 - prefix) + ip.b = 0 + } else { + ip.b = (ip.b >> (128 - prefix)) << (128 - prefix) + } + return ip +} + +func (m *GeoIPMatcher) Init(cidrs []*CIDR) error { + ip4Count := 0 + ip6Count := 0 + + for _, cidr := range cidrs { + ip := cidr.Ip + switch len(ip) { + case 4: + ip4Count++ + case 16: + ip6Count++ + default: + return fmt.Errorf("unexpect ip length: %d", len(ip)) + } + } + + cidrList := CIDRList(cidrs) + sort.Sort(&cidrList) + + m.ip4 = make([]uint32, 0, ip4Count) + m.prefix4 = make([]uint8, 0, ip4Count) + m.ip6 = make([]ipv6, 0, ip6Count) + m.prefix6 = make([]uint8, 0, ip6Count) + + for _, cidr := range cidrs { + ip := cidr.Ip + prefix := uint8(cidr.Prefix) + switch len(ip) { + case 4: + m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix)) + m.prefix4 = append(m.prefix4, prefix) + case 16: + ip6 := ipv6{ + a: binary.BigEndian.Uint64(ip[0:8]), + b: binary.BigEndian.Uint64(ip[8:16]), + } + ip6 = normalize6(ip6, prefix) + + m.ip6 = append(m.ip6, ip6) + m.prefix6 = append(m.prefix6, prefix) + } + } + + return nil +} + +func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) { + m.reverseMatch = isReverseMatch +} + +func (m *GeoIPMatcher) match4(ip uint32) bool { + if len(m.ip4) == 0 { + return false + } + + if ip < m.ip4[0] { + return false + } + + size := uint32(len(m.ip4)) + l := uint32(0) + r := size + for l < r { + x := ((l + r) >> 1) + if ip < m.ip4[x] { + r = x + continue + } + + nip := normalize4(ip, m.prefix4[x]) + if nip == m.ip4[x] { + return true + } + + l = x + 1 + } + + return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1] +} + +func less6(a ipv6, b ipv6) bool { + return a.a < b.a || (a.a == b.a && a.b < b.b) +} + +func (m *GeoIPMatcher) match6(ip ipv6) bool { + if len(m.ip6) == 0 { + return false + } + + if less6(ip, m.ip6[0]) { + return false + } + + size := uint32(len(m.ip6)) + l := uint32(0) + r := size + for l < r { + x := (l + r) / 2 + if less6(ip, m.ip6[x]) { + r = x + continue + } + + if normalize6(ip, m.prefix6[x]) == m.ip6[x] { + return true + } + + l = x + 1 + } + + return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1] +} + +// Match returns true if the given ip is included by the GeoIP. +func (m *GeoIPMatcher) Match(ip net.IP) bool { + switch len(ip) { + case 4: + if m.reverseMatch { + return !m.match4(binary.BigEndian.Uint32(ip)) + } + return m.match4(binary.BigEndian.Uint32(ip)) + case 16: + if m.reverseMatch { + return !m.match6(ipv6{ + a: binary.BigEndian.Uint64(ip[0:8]), + b: binary.BigEndian.Uint64(ip[8:16]), + }) + } + return m.match6(ipv6{ + a: binary.BigEndian.Uint64(ip[0:8]), + b: binary.BigEndian.Uint64(ip[8:16]), + }) + default: + return false + } +} + +// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code. +type GeoIPMatcherContainer struct { + matchers []*GeoIPMatcher +} + +// Add adds a new GeoIP set into the container. +// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one. +func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) { + if len(geoip.CountryCode) > 0 { + for _, m := range c.matchers { + if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch { + return m, nil + } + } + } + + m := &GeoIPMatcher{ + countryCode: geoip.CountryCode, + reverseMatch: geoip.ReverseMatch, + } + if err := m.Init(geoip.Cidr); err != nil { + return nil, err + } + if len(geoip.CountryCode) > 0 { + c.matchers = append(c.matchers, m) + } + return m, nil +} + +var globalGeoIPContainer GeoIPMatcherContainer diff --git a/rule/geodata/router/config.pb.go b/rule/geodata/router/config.pb.go new file mode 100644 index 00000000..3f97c80b --- /dev/null +++ b/rule/geodata/router/config.pb.go @@ -0,0 +1,720 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: config.proto + +package router + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Type of domain value. +type Domain_Type int32 + +const ( + // The value is used as is. + Domain_Plain Domain_Type = 0 + // The value is used as a regular expression. + Domain_Regex Domain_Type = 1 + // The value is a root domain. + Domain_Domain Domain_Type = 2 + // The value is a domain. + Domain_Full Domain_Type = 3 +) + +// Enum value maps for Domain_Type. +var ( + Domain_Type_name = map[int32]string{ + 0: "Plain", + 1: "Regex", + 2: "Domain", + 3: "Full", + } + Domain_Type_value = map[string]int32{ + "Plain": 0, + "Regex": 1, + "Domain": 2, + "Full": 3, + } +) + +func (x Domain_Type) Enum() *Domain_Type { + p := new(Domain_Type) + *p = x + return p +} + +func (x Domain_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Domain_Type) Descriptor() protoreflect.EnumDescriptor { + return file_config_proto_enumTypes[0].Descriptor() +} + +func (Domain_Type) Type() protoreflect.EnumType { + return &file_config_proto_enumTypes[0] +} + +func (x Domain_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Domain_Type.Descriptor instead. +func (Domain_Type) EnumDescriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{0, 0} +} + +// Domain for routing decision. +type Domain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Domain matching type. + Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=clash.rule.geodata.router.Domain_Type" json:"type,omitempty"` + // Domain value. + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // Attributes of this domain. May be used for filtering. + Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"` +} + +func (x *Domain) Reset() { + *x = Domain{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Domain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Domain) ProtoMessage() {} + +func (x *Domain) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Domain.ProtoReflect.Descriptor instead. +func (*Domain) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Domain) GetType() Domain_Type { + if x != nil { + return x.Type + } + return Domain_Plain +} + +func (x *Domain) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *Domain) GetAttribute() []*Domain_Attribute { + if x != nil { + return x.Attribute + } + return nil +} + +// IP for routing decision, in CIDR form. +type CIDR struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // IP address, should be either 4 or 16 bytes. + Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + // Number of leading ones in the network mask. + Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"` +} + +func (x *CIDR) Reset() { + *x = CIDR{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CIDR) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CIDR) ProtoMessage() {} + +func (x *CIDR) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CIDR.ProtoReflect.Descriptor instead. +func (*CIDR) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{1} +} + +func (x *CIDR) GetIp() []byte { + if x != nil { + return x.Ip + } + return nil +} + +func (x *CIDR) GetPrefix() uint32 { + if x != nil { + return x.Prefix + } + return 0 +} + +type GeoIP struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"` + ReverseMatch bool `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"` +} + +func (x *GeoIP) Reset() { + *x = GeoIP{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeoIP) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeoIP) ProtoMessage() {} + +func (x *GeoIP) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead. +func (*GeoIP) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{2} +} + +func (x *GeoIP) GetCountryCode() string { + if x != nil { + return x.CountryCode + } + return "" +} + +func (x *GeoIP) GetCidr() []*CIDR { + if x != nil { + return x.Cidr + } + return nil +} + +func (x *GeoIP) GetReverseMatch() bool { + if x != nil { + return x.ReverseMatch + } + return false +} + +type GeoIPList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"` +} + +func (x *GeoIPList) Reset() { + *x = GeoIPList{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeoIPList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeoIPList) ProtoMessage() {} + +func (x *GeoIPList) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead. +func (*GeoIPList) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{3} +} + +func (x *GeoIPList) GetEntry() []*GeoIP { + if x != nil { + return x.Entry + } + return nil +} + +type GeoSite struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"` +} + +func (x *GeoSite) Reset() { + *x = GeoSite{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeoSite) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeoSite) ProtoMessage() {} + +func (x *GeoSite) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead. +func (*GeoSite) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{4} +} + +func (x *GeoSite) GetCountryCode() string { + if x != nil { + return x.CountryCode + } + return "" +} + +func (x *GeoSite) GetDomain() []*Domain { + if x != nil { + return x.Domain + } + return nil +} + +type GeoSiteList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"` +} + +func (x *GeoSiteList) Reset() { + *x = GeoSiteList{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeoSiteList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeoSiteList) ProtoMessage() {} + +func (x *GeoSiteList) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead. +func (*GeoSiteList) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{5} +} + +func (x *GeoSiteList) GetEntry() []*GeoSite { + if x != nil { + return x.Entry + } + return nil +} + +type Domain_Attribute struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // Types that are assignable to TypedValue: + // *Domain_Attribute_BoolValue + // *Domain_Attribute_IntValue + TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"` +} + +func (x *Domain_Attribute) Reset() { + *x = Domain_Attribute{} + if protoimpl.UnsafeEnabled { + mi := &file_config_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Domain_Attribute) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Domain_Attribute) ProtoMessage() {} + +func (x *Domain_Attribute) ProtoReflect() protoreflect.Message { + mi := &file_config_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead. +func (*Domain_Attribute) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Domain_Attribute) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (m *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue { + if m != nil { + return m.TypedValue + } + return nil +} + +func (x *Domain_Attribute) GetBoolValue() bool { + if x, ok := x.GetTypedValue().(*Domain_Attribute_BoolValue); ok { + return x.BoolValue + } + return false +} + +func (x *Domain_Attribute) GetIntValue() int64 { + if x, ok := x.GetTypedValue().(*Domain_Attribute_IntValue); ok { + return x.IntValue + } + return 0 +} + +type isDomain_Attribute_TypedValue interface { + isDomain_Attribute_TypedValue() +} + +type Domain_Attribute_BoolValue struct { + BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"` +} + +type Domain_Attribute_IntValue struct { + IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"` +} + +func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {} + +func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {} + +var File_config_proto protoreflect.FileDescriptor + +var file_config_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, + 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x22, 0xc7, 0x02, 0x0a, 0x06, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, 0x75, 0x6c, 0x65, 0x2e, + 0x67, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x49, 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6c, 0x61, 0x73, + 0x68, 0x2e, 0x72, 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x1a, 0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x32, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, + 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, 0x0a, + 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, + 0x6c, 0x10, 0x03, 0x22, 0x2e, 0x0a, 0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x22, 0x84, 0x01, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, + 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x22, 0x43, 0x0a, 0x09, 0x47, 0x65, + 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, + 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, + 0x67, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x39, 0x0a, + 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x47, 0x0a, 0x0b, 0x47, 0x65, 0x6f, 0x53, + 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, + 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, + 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x72, + 0x75, 0x6c, 0x65, 0x2e, 0x67, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x44, 0x72, 0x65, 0x61, 0x6d, 0x61, 0x63, 0x72, 0x6f, 0x2f, 0x63, 0x6c, 0x61, 0x73, 0x68, + 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x19, 0x43, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_config_proto_rawDescOnce sync.Once + file_config_proto_rawDescData = file_config_proto_rawDesc +) + +func file_config_proto_rawDescGZIP() []byte { + file_config_proto_rawDescOnce.Do(func() { + file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData) + }) + return file_config_proto_rawDescData +} + +var file_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_config_proto_goTypes = []interface{}{ + (Domain_Type)(0), // 0: clash.rule.geodata.router.Domain.Type + (*Domain)(nil), // 1: clash.rule.geodata.router.Domain + (*CIDR)(nil), // 2: clash.rule.geodata.router.CIDR + (*GeoIP)(nil), // 3: clash.rule.geodata.router.GeoIP + (*GeoIPList)(nil), // 4: clash.rule.geodata.router.GeoIPList + (*GeoSite)(nil), // 5: clash.rule.geodata.router.GeoSite + (*GeoSiteList)(nil), // 6: clash.rule.geodata.router.GeoSiteList + (*Domain_Attribute)(nil), // 7: clash.rule.geodata.router.Domain.Attribute +} +var file_config_proto_depIdxs = []int32{ + 0, // 0: clash.rule.geodata.router.Domain.type:type_name -> clash.rule.geodata.router.Domain.Type + 7, // 1: clash.rule.geodata.router.Domain.attribute:type_name -> clash.rule.geodata.router.Domain.Attribute + 2, // 2: clash.rule.geodata.router.GeoIP.cidr:type_name -> clash.rule.geodata.router.CIDR + 3, // 3: clash.rule.geodata.router.GeoIPList.entry:type_name -> clash.rule.geodata.router.GeoIP + 1, // 4: clash.rule.geodata.router.GeoSite.domain:type_name -> clash.rule.geodata.router.Domain + 5, // 5: clash.rule.geodata.router.GeoSiteList.entry:type_name -> clash.rule.geodata.router.GeoSite + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_config_proto_init() } +func file_config_proto_init() { + if File_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Domain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CIDR); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeoIP); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeoIPList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeoSite); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeoSiteList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Domain_Attribute); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_config_proto_msgTypes[6].OneofWrappers = []interface{}{ + (*Domain_Attribute_BoolValue)(nil), + (*Domain_Attribute_IntValue)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_config_proto_rawDesc, + NumEnums: 1, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_config_proto_goTypes, + DependencyIndexes: file_config_proto_depIdxs, + EnumInfos: file_config_proto_enumTypes, + MessageInfos: file_config_proto_msgTypes, + }.Build() + File_config_proto = out.File + file_config_proto_rawDesc = nil + file_config_proto_goTypes = nil + file_config_proto_depIdxs = nil +} diff --git a/rule/geodata/router/config.proto b/rule/geodata/router/config.proto new file mode 100644 index 00000000..9e327d1a --- /dev/null +++ b/rule/geodata/router/config.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package clash.rule.geodata.router; +option csharp_namespace = "Clash.Rule.Geodata.Router"; +option go_package = "github.com/Dreamacro/clash/rule/geodata/router"; +option java_package = "com.clash.rule.geodata.router"; +option java_multiple_files = true; + +// Domain for routing decision. +message Domain { + // Type of domain value. + enum Type { + // The value is used as is. + Plain = 0; + // The value is used as a regular expression. + Regex = 1; + // The value is a root domain. + Domain = 2; + // The value is a domain. + Full = 3; + } + + // Domain matching type. + Type type = 1; + + // Domain value. + string value = 2; + + message Attribute { + string key = 1; + + oneof typed_value { + bool bool_value = 2; + int64 int_value = 3; + } + } + + // Attributes of this domain. May be used for filtering. + repeated Attribute attribute = 3; +} + +// IP for routing decision, in CIDR form. +message CIDR { + // IP address, should be either 4 or 16 bytes. + bytes ip = 1; + + // Number of leading ones in the network mask. + uint32 prefix = 2; +} + +message GeoIP { + string country_code = 1; + repeated CIDR cidr = 2; + bool reverse_match = 3; +} + +message GeoIPList { + repeated GeoIP entry = 1; +} + +message GeoSite { + string country_code = 1; + repeated Domain domain = 2; +} + +message GeoSiteList { + repeated GeoSite entry = 1; +} diff --git a/rule/geodata/standard/standard.go b/rule/geodata/standard/standard.go new file mode 100644 index 00000000..21e437a3 --- /dev/null +++ b/rule/geodata/standard/standard.go @@ -0,0 +1,81 @@ +package standard + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/rule/geodata" + "github.com/Dreamacro/clash/rule/geodata/router" + "google.golang.org/protobuf/proto" +) + +func ReadFile(path string) ([]byte, error) { + reader, err := os.Open(path) + if err != nil { + return nil, err + } + defer reader.Close() + + return ioutil.ReadAll(reader) +} + +func ReadAsset(file string) ([]byte, error) { + return ReadFile(C.Path.GetAssetLocation(file)) +} + +func loadIP(filename, country string) ([]*router.CIDR, error) { + geoipBytes, err := ReadAsset(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error()) + } + var geoipList router.GeoIPList + if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { + return nil, err + } + + for _, geoip := range geoipList.Entry { + if strings.EqualFold(geoip.CountryCode, country) { + return geoip.Cidr, nil + } + } + + return nil, fmt.Errorf("country not found in %s%s%s", filename, ": ", country) +} + +func loadSite(filename, list string) ([]*router.Domain, error) { + geositeBytes, err := ReadAsset(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error()) + } + var geositeList router.GeoSiteList + if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { + return nil, err + } + + for _, site := range geositeList.Entry { + if strings.EqualFold(site.CountryCode, list) { + return site.Domain, nil + } + } + + return nil, fmt.Errorf("list not found in %s%s%s", filename, ": ", list) +} + +type standardLoader struct{} + +func (d standardLoader) LoadSite(filename, list string) ([]*router.Domain, error) { + return loadSite(filename, list) +} + +func (d standardLoader) LoadIP(filename, country string) ([]*router.CIDR, error) { + return loadIP(filename, country) +} + +func init() { + geodata.RegisterGeoDataLoaderImplementationCreator("standard", func() geodata.LoaderImplementation { + return standardLoader{} + }) +} diff --git a/rule/geodata/strmatcher/ac_automaton_matcher.go b/rule/geodata/strmatcher/ac_automaton_matcher.go new file mode 100644 index 00000000..b9a171e0 --- /dev/null +++ b/rule/geodata/strmatcher/ac_automaton_matcher.go @@ -0,0 +1,243 @@ +package strmatcher + +import ( + "container/list" +) + +const validCharCount = 53 + +type MatchType struct { + matchType Type + exist bool +} + +const ( + TrieEdge bool = true + FailEdge bool = false +) + +type Edge struct { + edgeType bool + nextNode int +} + +type ACAutomaton struct { + trie [][validCharCount]Edge + fail []int + exists []MatchType + count int +} + +func newNode() [validCharCount]Edge { + var s [validCharCount]Edge + for i := range s { + s[i] = Edge{ + edgeType: FailEdge, + nextNode: 0, + } + } + return s +} + +var char2Index = []int{ + 'A': 0, + 'a': 0, + 'B': 1, + 'b': 1, + 'C': 2, + 'c': 2, + 'D': 3, + 'd': 3, + 'E': 4, + 'e': 4, + 'F': 5, + 'f': 5, + 'G': 6, + 'g': 6, + 'H': 7, + 'h': 7, + 'I': 8, + 'i': 8, + 'J': 9, + 'j': 9, + 'K': 10, + 'k': 10, + 'L': 11, + 'l': 11, + 'M': 12, + 'm': 12, + 'N': 13, + 'n': 13, + 'O': 14, + 'o': 14, + 'P': 15, + 'p': 15, + 'Q': 16, + 'q': 16, + 'R': 17, + 'r': 17, + 'S': 18, + 's': 18, + 'T': 19, + 't': 19, + 'U': 20, + 'u': 20, + 'V': 21, + 'v': 21, + 'W': 22, + 'w': 22, + 'X': 23, + 'x': 23, + 'Y': 24, + 'y': 24, + 'Z': 25, + 'z': 25, + '!': 26, + '$': 27, + '&': 28, + '\'': 29, + '(': 30, + ')': 31, + '*': 32, + '+': 33, + ',': 34, + ';': 35, + '=': 36, + ':': 37, + '%': 38, + '-': 39, + '.': 40, + '_': 41, + '~': 42, + '0': 43, + '1': 44, + '2': 45, + '3': 46, + '4': 47, + '5': 48, + '6': 49, + '7': 50, + '8': 51, + '9': 52, +} + +func NewACAutomaton() *ACAutomaton { + ac := new(ACAutomaton) + ac.trie = append(ac.trie, newNode()) + ac.fail = append(ac.fail, 0) + ac.exists = append(ac.exists, MatchType{ + matchType: Full, + exist: false, + }) + return ac +} + +func (ac *ACAutomaton) Add(domain string, t Type) { + node := 0 + for i := len(domain) - 1; i >= 0; i-- { + idx := char2Index[domain[i]] + if ac.trie[node][idx].nextNode == 0 { + ac.count++ + if len(ac.trie) < ac.count+1 { + ac.trie = append(ac.trie, newNode()) + ac.fail = append(ac.fail, 0) + ac.exists = append(ac.exists, MatchType{ + matchType: Full, + exist: false, + }) + } + ac.trie[node][idx] = Edge{ + edgeType: TrieEdge, + nextNode: ac.count, + } + } + node = ac.trie[node][idx].nextNode + } + ac.exists[node] = MatchType{ + matchType: t, + exist: true, + } + switch t { + case Domain: + ac.exists[node] = MatchType{ + matchType: Full, + exist: true, + } + idx := char2Index['.'] + if ac.trie[node][idx].nextNode == 0 { + ac.count++ + if len(ac.trie) < ac.count+1 { + ac.trie = append(ac.trie, newNode()) + ac.fail = append(ac.fail, 0) + ac.exists = append(ac.exists, MatchType{ + matchType: Full, + exist: false, + }) + } + ac.trie[node][idx] = Edge{ + edgeType: TrieEdge, + nextNode: ac.count, + } + } + node = ac.trie[node][idx].nextNode + ac.exists[node] = MatchType{ + matchType: t, + exist: true, + } + default: + break + } +} + +func (ac *ACAutomaton) Build() { + queue := list.New() + for i := 0; i < validCharCount; i++ { + if ac.trie[0][i].nextNode != 0 { + queue.PushBack(ac.trie[0][i]) + } + } + for { + front := queue.Front() + if front == nil { + break + } else { + node := front.Value.(Edge).nextNode + queue.Remove(front) + for i := 0; i < validCharCount; i++ { + if ac.trie[node][i].nextNode != 0 { + ac.fail[ac.trie[node][i].nextNode] = ac.trie[ac.fail[node]][i].nextNode + queue.PushBack(ac.trie[node][i]) + } else { + ac.trie[node][i] = Edge{ + edgeType: FailEdge, + nextNode: ac.trie[ac.fail[node]][i].nextNode, + } + } + } + } + } +} + +func (ac *ACAutomaton) Match(s string) bool { + node := 0 + fullMatch := true + // 1. the match string is all through trie edge. FULL MATCH or DOMAIN + // 2. the match string is through a fail edge. NOT FULL MATCH + // 2.1 Through a fail edge, but there exists a valid node. SUBSTR + for i := len(s) - 1; i >= 0; i-- { + idx := char2Index[s[i]] + fullMatch = fullMatch && ac.trie[node][idx].edgeType + node = ac.trie[node][idx].nextNode + switch ac.exists[node].matchType { + case Substr: + return true + case Domain: + if fullMatch { + return true + } + //default: + // //break // ineffective break statement, code "break" can not pass staticcheck check. don't sure that mean, so just block it. + } + } + return fullMatch && ac.exists[node].exist +} diff --git a/rule/geodata/strmatcher/domain_matcher.go b/rule/geodata/strmatcher/domain_matcher.go new file mode 100644 index 00000000..ae8e65bc --- /dev/null +++ b/rule/geodata/strmatcher/domain_matcher.go @@ -0,0 +1,98 @@ +package strmatcher + +import "strings" + +func breakDomain(domain string) []string { + return strings.Split(domain, ".") +} + +type node struct { + values []uint32 + sub map[string]*node +} + +// DomainMatcherGroup is a IndexMatcher for a large set of Domain matchers. +// Visible for testing only. +type DomainMatcherGroup struct { + root *node +} + +func (g *DomainMatcherGroup) Add(domain string, value uint32) { + if g.root == nil { + g.root = new(node) + } + + current := g.root + parts := breakDomain(domain) + for i := len(parts) - 1; i >= 0; i-- { + part := parts[i] + if current.sub == nil { + current.sub = make(map[string]*node) + } + next := current.sub[part] + if next == nil { + next = new(node) + current.sub[part] = next + } + current = next + } + + current.values = append(current.values, value) +} + +func (g *DomainMatcherGroup) addMatcher(m domainMatcher, value uint32) { + g.Add(string(m), value) +} + +func (g *DomainMatcherGroup) Match(domain string) []uint32 { + if domain == "" { + return nil + } + + current := g.root + if current == nil { + return nil + } + + nextPart := func(idx int) int { + for i := idx - 1; i >= 0; i-- { + if domain[i] == '.' { + return i + } + } + return -1 + } + + matches := [][]uint32{} + idx := len(domain) + for { + if idx == -1 || current.sub == nil { + break + } + + nidx := nextPart(idx) + part := domain[nidx+1 : idx] + next := current.sub[part] + if next == nil { + break + } + current = next + idx = nidx + if len(current.values) > 0 { + matches = append(matches, current.values) + } + } + switch len(matches) { + case 0: + return nil + case 1: + return matches[0] + default: + result := []uint32{} + for idx := range matches { + // Insert reversely, the subdomain that matches further ranks higher + result = append(result, matches[len(matches)-1-idx]...) + } + return result + } +} diff --git a/rule/geodata/strmatcher/full_matcher.go b/rule/geodata/strmatcher/full_matcher.go new file mode 100644 index 00000000..e00d02aa --- /dev/null +++ b/rule/geodata/strmatcher/full_matcher.go @@ -0,0 +1,25 @@ +package strmatcher + +type FullMatcherGroup struct { + matchers map[string][]uint32 +} + +func (g *FullMatcherGroup) Add(domain string, value uint32) { + if g.matchers == nil { + g.matchers = make(map[string][]uint32) + } + + g.matchers[domain] = append(g.matchers[domain], value) +} + +func (g *FullMatcherGroup) addMatcher(m fullMatcher, value uint32) { + g.Add(string(m), value) +} + +func (g *FullMatcherGroup) Match(str string) []uint32 { + if g.matchers == nil { + return nil + } + + return g.matchers[str] +} diff --git a/rule/geodata/strmatcher/matchers.go b/rule/geodata/strmatcher/matchers.go new file mode 100644 index 00000000..b5ab09c4 --- /dev/null +++ b/rule/geodata/strmatcher/matchers.go @@ -0,0 +1,52 @@ +package strmatcher + +import ( + "regexp" + "strings" +) + +type fullMatcher string + +func (m fullMatcher) Match(s string) bool { + return string(m) == s +} + +func (m fullMatcher) String() string { + return "full:" + string(m) +} + +type substrMatcher string + +func (m substrMatcher) Match(s string) bool { + return strings.Contains(s, string(m)) +} + +func (m substrMatcher) String() string { + return "keyword:" + string(m) +} + +type domainMatcher string + +func (m domainMatcher) Match(s string) bool { + pattern := string(m) + if !strings.HasSuffix(s, pattern) { + return false + } + return len(s) == len(pattern) || s[len(s)-len(pattern)-1] == '.' +} + +func (m domainMatcher) String() string { + return "domain:" + string(m) +} + +type regexMatcher struct { + pattern *regexp.Regexp +} + +func (m *regexMatcher) Match(s string) bool { + return m.pattern.MatchString(s) +} + +func (m *regexMatcher) String() string { + return "regexp:" + m.pattern.String() +} diff --git a/rule/geodata/strmatcher/mph_matcher.go b/rule/geodata/strmatcher/mph_matcher.go new file mode 100644 index 00000000..3c10cb49 --- /dev/null +++ b/rule/geodata/strmatcher/mph_matcher.go @@ -0,0 +1,304 @@ +package strmatcher + +import ( + "math/bits" + "regexp" + "sort" + "strings" + "unsafe" +) + +// PrimeRK is the prime base used in Rabin-Karp algorithm. +const PrimeRK = 16777619 + +// calculate the rolling murmurHash of given string +func RollingHash(s string) uint32 { + h := uint32(0) + for i := len(s) - 1; i >= 0; i-- { + h = h*PrimeRK + uint32(s[i]) + } + return h +} + +// A MphMatcherGroup is divided into three parts: +// 1. `full` and `domain` patterns are matched by Rabin-Karp algorithm and minimal perfect hash table; +// 2. `substr` patterns are matched by ac automaton; +// 3. `regex` patterns are matched with the regex library. +type MphMatcherGroup struct { + ac *ACAutomaton + otherMatchers []matcherEntry + rules []string + level0 []uint32 + level0Mask int + level1 []uint32 + level1Mask int + count uint32 + ruleMap *map[string]uint32 +} + +func (g *MphMatcherGroup) AddFullOrDomainPattern(pattern string, t Type) { + h := RollingHash(pattern) + switch t { + case Domain: + (*g.ruleMap)["."+pattern] = h*PrimeRK + uint32('.') + fallthrough + case Full: + (*g.ruleMap)[pattern] = h + default: + } +} + +func NewMphMatcherGroup() *MphMatcherGroup { + return &MphMatcherGroup{ + ac: nil, + otherMatchers: nil, + rules: nil, + level0: nil, + level0Mask: 0, + level1: nil, + level1Mask: 0, + count: 1, + ruleMap: &map[string]uint32{}, + } +} + +// AddPattern adds a pattern to MphMatcherGroup +func (g *MphMatcherGroup) AddPattern(pattern string, t Type) (uint32, error) { + switch t { + case Substr: + if g.ac == nil { + g.ac = NewACAutomaton() + } + g.ac.Add(pattern, t) + case Full, Domain: + pattern = strings.ToLower(pattern) + g.AddFullOrDomainPattern(pattern, t) + case Regex: + r, err := regexp.Compile(pattern) + if err != nil { + return 0, err + } + g.otherMatchers = append(g.otherMatchers, matcherEntry{ + m: ®exMatcher{pattern: r}, + id: g.count, + }) + default: + panic("Unknown type") + } + return g.count, nil +} + +// Build builds a minimal perfect hash table and ac automaton from insert rules +func (g *MphMatcherGroup) Build() { + if g.ac != nil { + g.ac.Build() + } + keyLen := len(*g.ruleMap) + if keyLen == 0 { + keyLen = 1 + (*g.ruleMap)["empty___"] = RollingHash("empty___") + } + g.level0 = make([]uint32, nextPow2(keyLen/4)) + g.level0Mask = len(g.level0) - 1 + g.level1 = make([]uint32, nextPow2(keyLen)) + g.level1Mask = len(g.level1) - 1 + sparseBuckets := make([][]int, len(g.level0)) + var ruleIdx int + for rule, hash := range *g.ruleMap { + n := int(hash) & g.level0Mask + g.rules = append(g.rules, rule) + sparseBuckets[n] = append(sparseBuckets[n], ruleIdx) + ruleIdx++ + } + g.ruleMap = nil + var buckets []indexBucket + for n, vals := range sparseBuckets { + if len(vals) > 0 { + buckets = append(buckets, indexBucket{n, vals}) + } + } + sort.Sort(bySize(buckets)) + + occ := make([]bool, len(g.level1)) + var tmpOcc []int + for _, bucket := range buckets { + seed := uint32(0) + for { + findSeed := true + tmpOcc = tmpOcc[:0] + for _, i := range bucket.vals { + n := int(strhashFallback(unsafe.Pointer(&g.rules[i]), uintptr(seed))) & g.level1Mask + if occ[n] { + for _, n := range tmpOcc { + occ[n] = false + } + seed++ + findSeed = false + break + } + occ[n] = true + tmpOcc = append(tmpOcc, n) + g.level1[n] = uint32(i) + } + if findSeed { + g.level0[bucket.n] = seed + break + } + } + } +} + +func nextPow2(v int) int { + if v <= 1 { + return 1 + } + const MaxUInt = ^uint(0) + n := (MaxUInt >> bits.LeadingZeros(uint(v))) + 1 + return int(n) +} + +// Lookup searches for s in t and returns its index and whether it was found. +func (g *MphMatcherGroup) Lookup(h uint32, s string) bool { + i0 := int(h) & g.level0Mask + seed := g.level0[i0] + i1 := int(strhashFallback(unsafe.Pointer(&s), uintptr(seed))) & g.level1Mask + n := g.level1[i1] + return s == g.rules[int(n)] +} + +// Match implements IndexMatcher.Match. +func (g *MphMatcherGroup) Match(pattern string) []uint32 { + result := []uint32{} + hash := uint32(0) + for i := len(pattern) - 1; i >= 0; i-- { + hash = hash*PrimeRK + uint32(pattern[i]) + if pattern[i] == '.' { + if g.Lookup(hash, pattern[i:]) { + result = append(result, 1) + return result + } + } + } + if g.Lookup(hash, pattern) { + result = append(result, 1) + return result + } + if g.ac != nil && g.ac.Match(pattern) { + result = append(result, 1) + return result + } + for _, e := range g.otherMatchers { + if e.m.Match(pattern) { + result = append(result, e.id) + return result + } + } + return nil +} + +type indexBucket struct { + n int + vals []int +} + +type bySize []indexBucket + +func (s bySize) Len() int { return len(s) } +func (s bySize) Less(i, j int) bool { return len(s[i].vals) > len(s[j].vals) } +func (s bySize) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type stringStruct struct { + str unsafe.Pointer + len int +} + +func strhashFallback(a unsafe.Pointer, h uintptr) uintptr { + x := (*stringStruct)(a) + return memhashFallback(x.str, h, uintptr(x.len)) +} + +const ( + // Constants for multiplication: four random odd 64-bit numbers. + m1 = 16877499708836156737 + m2 = 2820277070424839065 + m3 = 9497967016996688599 + m4 = 15839092249703872147 +) + +var hashkey = [4]uintptr{1, 1, 1, 1} + +func memhashFallback(p unsafe.Pointer, seed, s uintptr) uintptr { + h := uint64(seed + s*hashkey[0]) +tail: + switch { + case s == 0: + case s < 4: + h ^= uint64(*(*byte)(p)) + h ^= uint64(*(*byte)(add(p, s>>1))) << 8 + h ^= uint64(*(*byte)(add(p, s-1))) << 16 + h = rotl31(h*m1) * m2 + case s <= 8: + h ^= uint64(readUnaligned32(p)) + h ^= uint64(readUnaligned32(add(p, s-4))) << 32 + h = rotl31(h*m1) * m2 + case s <= 16: + h ^= readUnaligned64(p) + h = rotl31(h*m1) * m2 + h ^= readUnaligned64(add(p, s-8)) + h = rotl31(h*m1) * m2 + case s <= 32: + h ^= readUnaligned64(p) + h = rotl31(h*m1) * m2 + h ^= readUnaligned64(add(p, 8)) + h = rotl31(h*m1) * m2 + h ^= readUnaligned64(add(p, s-16)) + h = rotl31(h*m1) * m2 + h ^= readUnaligned64(add(p, s-8)) + h = rotl31(h*m1) * m2 + default: + v1 := h + v2 := uint64(seed * hashkey[1]) + v3 := uint64(seed * hashkey[2]) + v4 := uint64(seed * hashkey[3]) + for s >= 32 { + v1 ^= readUnaligned64(p) + v1 = rotl31(v1*m1) * m2 + p = add(p, 8) + v2 ^= readUnaligned64(p) + v2 = rotl31(v2*m2) * m3 + p = add(p, 8) + v3 ^= readUnaligned64(p) + v3 = rotl31(v3*m3) * m4 + p = add(p, 8) + v4 ^= readUnaligned64(p) + v4 = rotl31(v4*m4) * m1 + p = add(p, 8) + s -= 32 + } + h = v1 ^ v2 ^ v3 ^ v4 + goto tail + } + + h ^= h >> 29 + h *= m3 + h ^= h >> 32 + return uintptr(h) +} + +func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(p) + x) +} + +func readUnaligned32(p unsafe.Pointer) uint32 { + q := (*[4]byte)(p) + return uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24 +} + +func rotl31(x uint64) uint64 { + return (x << 31) | (x >> (64 - 31)) +} + +func readUnaligned64(p unsafe.Pointer) uint64 { + q := (*[8]byte)(p) + return uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 | uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56 +} diff --git a/rule/geodata/strmatcher/strmatcher.go b/rule/geodata/strmatcher/strmatcher.go new file mode 100644 index 00000000..294e6e73 --- /dev/null +++ b/rule/geodata/strmatcher/strmatcher.go @@ -0,0 +1,107 @@ +package strmatcher + +import ( + "regexp" +) + +// Matcher is the interface to determine a string matches a pattern. +type Matcher interface { + // Match returns true if the given string matches a predefined pattern. + Match(string) bool + String() string +} + +// Type is the type of the matcher. +type Type byte + +const ( + // Full is the type of matcher that the input string must exactly equal to the pattern. + Full Type = iota + // Substr is the type of matcher that the input string must contain the pattern as a sub-string. + Substr + // Domain is the type of matcher that the input string must be a sub-domain or itself of the pattern. + Domain + // Regex is the type of matcher that the input string must matches the regular-expression pattern. + Regex +) + +// New creates a new Matcher based on the given pattern. +func (t Type) New(pattern string) (Matcher, error) { + // 1. regex matching is case-sensitive + switch t { + case Full: + return fullMatcher(pattern), nil + case Substr: + return substrMatcher(pattern), nil + case Domain: + return domainMatcher(pattern), nil + case Regex: + r, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + return ®exMatcher{ + pattern: r, + }, nil + default: + panic("Unknown type") + } +} + +// IndexMatcher is the interface for matching with a group of matchers. +type IndexMatcher interface { + // Match returns the index of a matcher that matches the input. It returns empty array if no such matcher exists. + Match(input string) []uint32 +} + +type matcherEntry struct { + m Matcher + id uint32 +} + +// MatcherGroup is an implementation of IndexMatcher. +// Empty initialization works. +type MatcherGroup struct { + count uint32 + fullMatcher FullMatcherGroup + domainMatcher DomainMatcherGroup + otherMatchers []matcherEntry +} + +// Add adds a new Matcher into the MatcherGroup, and returns its index. The index will never be 0. +func (g *MatcherGroup) Add(m Matcher) uint32 { + g.count++ + c := g.count + + switch tm := m.(type) { + case fullMatcher: + g.fullMatcher.addMatcher(tm, c) + case domainMatcher: + g.domainMatcher.addMatcher(tm, c) + default: + g.otherMatchers = append(g.otherMatchers, matcherEntry{ + m: m, + id: c, + }) + } + + return c +} + +// Match implements IndexMatcher.Match. +func (g *MatcherGroup) Match(pattern string) []uint32 { + result := []uint32{} + result = append(result, g.fullMatcher.Match(pattern)...) + result = append(result, g.domainMatcher.Match(pattern)...) + for _, e := range g.otherMatchers { + if e.m.Match(pattern) { + result = append(result, e.id) + } + } + return result +} + +// Size returns the number of matchers in the MatcherGroup. +func (g *MatcherGroup) Size() uint32 { + return g.count +} diff --git a/rule/geoip.go b/rule/geoip.go index be4b5029..d494d46e 100644 --- a/rule/geoip.go +++ b/rule/geoip.go @@ -1,14 +1,22 @@ package rules import ( - "github.com/Dreamacro/clash/component/mmdb" + "fmt" + "strings" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/rule/geodata" + "github.com/Dreamacro/clash/rule/geodata/router" + _ "github.com/Dreamacro/clash/rule/geodata/standard" ) type GEOIP struct { - country string - adapter string - noResolveIP bool + country string + adapter string + noResolveIP bool + network C.NetWork + geoIPMatcher *router.GeoIPMatcher } func (g *GEOIP) RuleType() C.RuleType { @@ -20,8 +28,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool { if ip == nil { return false } - record, _ := mmdb.Instance().Country(ip) - return record.Country.IsoCode == g.country + return g.geoIPMatcher.Match(ip) } func (g *GEOIP) Adapter() string { @@ -36,12 +43,44 @@ func (g *GEOIP) ShouldResolveIP() bool { return !g.noResolveIP } -func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { - geoip := &GEOIP{ - country: country, - adapter: adapter, - noResolveIP: noResolveIP, +func (g *GEOIP) NetWork() C.NetWork { + return g.network +} + +func NewGEOIP(country string, adapter string, noResolveIP bool, network C.NetWork) (*GEOIP, error) { + geoLoaderName := "standard" + //geoLoaderName := "memconservative" + geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName) + if err != nil { + return nil, fmt.Errorf("[GeoIP] %s", err.Error()) } - return geoip + records, err := geoLoader.LoadGeoIP(strings.ReplaceAll(country, "!", "")) + if err != nil { + return nil, fmt.Errorf("[GeoIP] %s", err.Error()) + } + + geoIP := &router.GeoIP{ + CountryCode: country, + Cidr: records, + ReverseMatch: strings.Contains(country, "!"), + } + + geoIPMatcher, err := router.NewGeoIPMatcher(geoIP) + + if err != nil { + return nil, fmt.Errorf("[GeoIP] %s", err.Error()) + } + + log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, len(records)) + + geoip := &GEOIP{ + country: country, + adapter: adapter, + noResolveIP: noResolveIP, + network: network, + geoIPMatcher: geoIPMatcher, + } + + return geoip, nil } diff --git a/rule/geosite.go b/rule/geosite.go new file mode 100644 index 00000000..15d3c271 --- /dev/null +++ b/rule/geosite.go @@ -0,0 +1,82 @@ +package rules + +import ( + "fmt" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/rule/geodata" + //_ "github.com/Dreamacro/clash/rule/geodata/memconservative" + "github.com/Dreamacro/clash/rule/geodata/router" + _ "github.com/Dreamacro/clash/rule/geodata/standard" +) + +type GEOSITE struct { + country string + adapter string + network C.NetWork + matcher *router.DomainMatcher +} + +func (gs *GEOSITE) RuleType() C.RuleType { + return C.GEOSITE +} + +func (gs *GEOSITE) Match(metadata *C.Metadata) bool { + if metadata.AddrType != C.AtypDomainName { + return false + } + + domain := metadata.Host + return gs.matcher.ApplyDomain(domain) +} + +func (gs *GEOSITE) Adapter() string { + return gs.adapter +} + +func (gs *GEOSITE) Payload() string { + return gs.country +} + +func (gs *GEOSITE) ShouldResolveIP() bool { + return false +} + +func (gs *GEOSITE) NetWork() C.NetWork { + return gs.network +} + +func NewGEOSITE(country string, adapter string, network C.NetWork) (*GEOSITE, error) { + geoLoaderName := "standard" + //geoLoaderName := "memconservative" + geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName) + if err != nil { + return nil, fmt.Errorf("[GeoSite] %s", err.Error()) + } + + domains, err := geoLoader.LoadGeoSite(country) + if err != nil { + return nil, fmt.Errorf("[GeoSite] %s", err.Error()) + } + + //linear: linear algorithm + //matcher, err := router.NewDomainMatcher(domains) + + //mph:minimal perfect hash algorithm + matcher, err := router.NewMphMatcherGroup(domains) + if err != nil { + return nil, fmt.Errorf("[GeoSite] %s", err.Error()) + } + + log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, len(domains)) + + geoSite := &GEOSITE{ + country: country, + adapter: adapter, + network: network, + matcher: matcher, + } + + return geoSite, nil +} diff --git a/rule/ipcidr.go b/rule/ipcidr.go index ff3b83c4..03fe2de3 100644 --- a/rule/ipcidr.go +++ b/rule/ipcidr.go @@ -23,6 +23,7 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { type IPCIDR struct { ipnet *net.IPNet adapter string + network C.NetWork isSourceIP bool noResolveIP bool } @@ -54,7 +55,11 @@ func (i *IPCIDR) ShouldResolveIP() bool { return !i.noResolveIP } -func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { +func (i *IPCIDR) NetWork() C.NetWork { + return i.network +} + +func NewIPCIDR(s string, adapter string, network C.NetWork, opts ...IPCIDROption) (*IPCIDR, error) { _, ipnet, err := net.ParseCIDR(s) if err != nil { return nil, errPayload @@ -63,6 +68,7 @@ func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) ipcidr := &IPCIDR{ ipnet: ipnet, adapter: adapter, + network: network, } for _, o := range opts { diff --git a/rule/parser.go b/rule/parser.go index 6e78b8fa..91e3f8de 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -10,29 +10,32 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { var ( parseErr error parsed C.Rule + network = findNetwork(params) ) switch tp { case "DOMAIN": - parsed = NewDomain(payload, target) + parsed = NewDomain(payload, target, network) case "DOMAIN-SUFFIX": - parsed = NewDomainSuffix(payload, target) + parsed = NewDomainSuffix(payload, target, network) case "DOMAIN-KEYWORD": - parsed = NewDomainKeyword(payload, target) + parsed = NewDomainKeyword(payload, target, network) + case "GEOSITE": + parsed, parseErr = NewGEOSITE(payload, target, network) case "GEOIP": noResolve := HasNoResolve(params) - parsed = NewGEOIP(payload, target, noResolve) + parsed, parseErr = NewGEOIP(payload, target, noResolve, network) case "IP-CIDR", "IP-CIDR6": noResolve := HasNoResolve(params) - parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) + parsed, parseErr = NewIPCIDR(payload, target, network, WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": - parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) + parsed, parseErr = NewIPCIDR(payload, target, network, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) case "SRC-PORT": - parsed, parseErr = NewPort(payload, target, true) + parsed, parseErr = NewPort(payload, target, true, network) case "DST-PORT": - parsed, parseErr = NewPort(payload, target, false) + parsed, parseErr = NewPort(payload, target, false, network) case "PROCESS-NAME": - parsed, parseErr = NewProcess(payload, target) + parsed, parseErr = NewProcess(payload, target, network) case "MATCH": parsed = NewMatch(target) default: diff --git a/rule/port.go b/rule/port.go index 281a4c4d..df2ee25b 100644 --- a/rule/port.go +++ b/rule/port.go @@ -10,6 +10,7 @@ type Port struct { adapter string port string isSource bool + network C.NetWork } func (p *Port) RuleType() C.RuleType { @@ -38,7 +39,11 @@ func (p *Port) ShouldResolveIP() bool { return false } -func NewPort(port string, adapter string, isSource bool) (*Port, error) { +func (p *Port) NetWork() C.NetWork { + return p.network +} + +func NewPort(port string, adapter string, isSource bool, network C.NetWork) (*Port, error) { _, err := strconv.Atoi(port) if err != nil { return nil, errPayload @@ -47,5 +52,6 @@ func NewPort(port string, adapter string, isSource bool) (*Port, error) { adapter: adapter, port: port, isSource: isSource, + network: network, }, nil } diff --git a/rule/process.go b/rule/process.go index 638ec723..784b6ae6 100644 --- a/rule/process.go +++ b/rule/process.go @@ -16,6 +16,7 @@ var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) type Process struct { adapter string process string + network C.NetWork } func (ps *Process) RuleType() C.RuleType { @@ -23,6 +24,17 @@ func (ps *Process) RuleType() C.RuleType { } func (ps *Process) Match(metadata *C.Metadata) bool { + + if metadata.Process != "" { + //log.Debugln("Use cache process: %s", metadata.Process) + return strings.EqualFold(metadata.Process, ps.process) + } + + // ignore match in proxy type "tproxy" + if metadata.Type == C.TPROXY { + return false + } + key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) cached, hit := processCache.Get(key) if !hit { @@ -42,7 +54,9 @@ func (ps *Process) Match(metadata *C.Metadata) bool { cached = name } - return strings.EqualFold(cached.(string), ps.process) + metadata.Process = cached.(string) + + return strings.EqualFold(metadata.Process, ps.process) } func (ps *Process) Adapter() string { @@ -57,9 +71,14 @@ func (ps *Process) ShouldResolveIP() bool { return false } -func NewProcess(process string, adapter string) (*Process, error) { +func (ps *Process) NetWork() C.NetWork { + return ps.network +} + +func NewProcess(process string, adapter string, network C.NetWork) (*Process, error) { return &Process{ adapter: adapter, process: process, + network: network, }, nil } diff --git a/transport/vless/conn.go b/transport/vless/conn.go new file mode 100644 index 00000000..ba309c09 --- /dev/null +++ b/transport/vless/conn.go @@ -0,0 +1,109 @@ +package vless + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "io/ioutil" + "net" + + "github.com/gofrs/uuid" +) + +/*var ( + + //proto.Marshal(addons) bytes for Flow: "xtls-rprx-direct" + addOnBytes, _ = hex.DecodeString("120a1078746c732d727072782d646972656374") + addOnBytesLen = len(addOnBytes) + + //proto.Marshal(addons) bytes for Flow: "" + //addOnEmptyBytes, _ = hex.DecodeString("00") + //addOnEmptyBytesLen = len(addOnEmptyBytes) +)*/ + +type Conn struct { + net.Conn + dst *DstAddr + id *uuid.UUID + + received bool +} + +func (vc *Conn) Read(b []byte) (int, error) { + if vc.received { + return vc.Conn.Read(b) + } + + if err := vc.recvResponse(); err != nil { + return 0, err + } + vc.received = true + return vc.Conn.Read(b) +} + +func (vc *Conn) sendRequest() error { + buf := &bytes.Buffer{} + + buf.WriteByte(Version) // protocol version + buf.Write(vc.id.Bytes()) // 16 bytes of uuid + + // command + if vc.dst.UDP { + buf.WriteByte(0) // addon data length. 0 means no addon data + //buf.WriteByte(byte(addOnEmptyBytesLen)) + //buf.Write(addOnEmptyBytes) + buf.WriteByte(CommandUDP) + } else { + buf.WriteByte(0) // addon data length. 0 means no addon data + //buf.WriteByte(byte(addOnBytesLen)) + //buf.Write(addOnBytes) + buf.WriteByte(CommandTCP) + } + + // Port AddrType Addr + binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) + buf.WriteByte(vc.dst.AddrType) + buf.Write(vc.dst.Addr) + + _, err := vc.Conn.Write(buf.Bytes()) + return err +} + +func (vc *Conn) recvResponse() error { + var err error + buf := make([]byte, 1) + _, err = io.ReadFull(vc.Conn, buf) + if err != nil { + return err + } + + if buf[0] != Version { + return errors.New("unexpected response version") + } + + _, err = io.ReadFull(vc.Conn, buf) + if err != nil { + return err + } + + length := int64(buf[0]) + if length != 0 { // addon data length > 0 + io.CopyN(ioutil.Discard, vc.Conn, length) // just discard + } + + return nil +} + +// newConn return a Conn instance +func newConn(conn net.Conn, id *uuid.UUID, dst *DstAddr) (*Conn, error) { + c := &Conn{ + Conn: conn, + id: id, + dst: dst, + } + if err := c.sendRequest(); err != nil { + return nil, err + } + return c, nil +} diff --git a/transport/vless/vless.go b/transport/vless/vless.go new file mode 100644 index 00000000..30309d71 --- /dev/null +++ b/transport/vless/vless.go @@ -0,0 +1,61 @@ +package vless + +import ( + "net" + + "github.com/gofrs/uuid" +) + +const Version byte = 0 // protocol version. preview version is 0 + +// Command types +const ( + CommandTCP byte = 1 + CommandUDP byte = 2 +) + +// Addr types +const ( + AtypIPv4 byte = 1 + AtypDomainName byte = 2 + AtypIPv6 byte = 3 +) + +// DstAddr store destination address +type DstAddr struct { + UDP bool + AddrType byte + Addr []byte + Port uint +} + +// Config of vless +type Config struct { + UUID string + AlterID uint16 + Security string + Port string + HostName string +} + +// Client is vless connection generator +type Client struct { + uuid *uuid.UUID +} + +// StreamConn return a Conn with net.Conn and DstAddr +func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { + return newConn(conn, c.uuid, dst) +} + +// NewClient return Client instance +func NewClient(uuidStr string) (*Client, error) { + uid, err := uuid.FromString(uuidStr) + if err != nil { + return nil, err + } + + return &Client{ + uuid: &uid, + }, nil +} diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index 1f5f1f9c..9f231547 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -77,6 +77,9 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R if rule != nil { t.trackerInfo.Rule = rule.RuleType().String() t.trackerInfo.RulePayload = rule.Payload() + //if rule.RuleType() == C.GEOSITE || rule.RuleType() == C.GEOIP { + // t.trackerInfo.Rule = t.trackerInfo.Rule + " (" + rule.Payload() + ")" + //} } manager.Join(t) @@ -134,6 +137,9 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru if rule != nil { ut.trackerInfo.Rule = rule.RuleType().String() ut.trackerInfo.RulePayload = rule.Payload() + //if rule.RuleType() == C.GEOSITE || rule.RuleType() == C.GEOIP { + // ut.trackerInfo.Rule = ut.trackerInfo.Rule + " (" + rule.Payload() + ")" + //} } manager.Join(ut) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index a40b697d..4bcb64da 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -14,6 +14,7 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" + R "github.com/Dreamacro/clash/rule" "github.com/Dreamacro/clash/tunnel/statistic" ) @@ -31,6 +32,11 @@ var ( // default timeout for UDP session udpTimeout = 60 * time.Second + + preProcessCacheFinder, _ = R.NewProcess("", "", C.ALLNet) + + fakeIpMask = net.IPv4Mask(0, 0, 0xff, 0xff) + fakeIpMaxIp = net.IPv4(0, 0, 255, 255) ) func init() { @@ -138,7 +144,7 @@ func preHandleMetadata(metadata *C.Metadata) error { // redir-host should lookup the hosts metadata.DstIP = node.Data.(net.IP) } - } else if resolver.IsFakeIP(metadata.DstIP) { + } else if resolver.IsFakeIP(metadata.DstIP) && !fakeIpMaxIp.Equal(metadata.DstIP.Mask(fakeIpMask)) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) } } @@ -230,13 +236,13 @@ func handleUDPConn(packet *inbound.PacketAdapter) { switch true { case rule != nil: - log.Infoln("[UDP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) + log.Infoln("[UDP] %s(%s) --> %s:%s match %s(%s) %s using %s", metadata.SourceAddress(), metadata.Process, metadata.String(), metadata.DstPort, rule.RuleType().String(), rule.Payload(), rule.NetWork().String(), rawPc.Chains().String()) case mode == Global: - log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.String()) case mode == Direct: - log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[UDP] %s(%s) --> %s using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.String()) default: - log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[UDP] %s(%s) --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.String()) } go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) @@ -280,13 +286,13 @@ func handleTCPConn(ctx C.ConnContext) { switch true { case rule != nil: - log.Infoln("[TCP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) + log.Infoln("[TCP] %s(%s) --> %s:%s match %s(%s) %s using %s", metadata.SourceAddress(), metadata.Process, metadata.String(), metadata.DstPort, rule.RuleType().String(), rule.Payload(), rule.NetWork().String(), remoteConn.Chains().String()) case mode == Global: - log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.String()) case mode == Direct: - log.Infoln("[TCP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[TCP] %s(%s) --> %s using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.String()) default: - log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[TCP] %s(%s) --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.Process, metadata.String()) } handleSocket(ctx, remoteConn) @@ -308,6 +314,9 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { resolved = true } + // preset process name and cache it + preProcessCacheFinder.Match(metadata) + for _, rule := range rules { if !resolved && shouldResolveIP(rule, metadata) { ip, err := resolver.ResolveIP(metadata.Host) @@ -330,6 +339,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { log.Debugln("%s UDP is not supported", adapter.Name()) continue } + + if rule.NetWork() != C.ALLNet && rule.NetWork() != metadata.NetWork { + continue + } return adapter, rule, nil } }