diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 2e5b75545..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[Bug]" -labels: '' -assignees: '' - ---- - - - ------------------------------------------------------------------- - - - -### Clash config - -
- config.yaml - -```yaml -…… -``` - -
- -### Clash log - -``` -…… -``` - -### 环境 Environment - -* 操作系统 (the OS that the Clash core is running on) -…… -* 网路环境或拓扑 (network conditions/topology) -…… -* iptables,如果适用 (if applicable) -…… -* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) -…… -* 其他 (any other information that would be useful) -…… - -### 说明 Description - - - -### 重现问题的具体布骤 Steps to Reproduce - -1. [First Step] -2. [Second Step] -3. …… - -**我预期会发生……?** - - -**实际上发生了什么?** - - -### 可能的解决方案 Possible Solution - - - - -### 更多信息 More Information diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..b56b98651 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,76 @@ +name: Bug report +description: Create a report to help us improve +title: "[Bug] " +body: + - type: checkboxes + id: ensure + attributes: + label: Verify steps + description: " +在提交之前,请确认 +Please verify that you've followed these steps +" + options: + - label: " +如果你可以自己 debug 并解决的话,提交 PR 吧 +Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome. +" + required: true + - label: " +我已经在 [Issue Tracker](……/) 中找过我要提出的问题 +I have searched on the [issue tracker](……/) for a related issue. +" + required: true + - label: " +我已经使用 dev 分支版本测试过,问题依旧存在 +I have tested using the dev branch, and the issue still exists. +" + required: true + - label: " +我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题 +I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue. +" + required: true + - label: " +这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题 +This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash. +" + required: true + - type: input + attributes: + label: Clash version + validations: + required: true + - type: dropdown + id: os + attributes: + label: What OS are you seeing the problem on? + multiple: true + options: + - macOS + - Windows + - Linux + - OpenBSD/FreeBSD + - type: textarea + attributes: + render: yaml + label: "Clash config" + description: " +在下方附上 Clash core 脱敏后配置文件的内容 +Paste the Clash core configuration below. +" + validations: + required: true + - type: textarea + attributes: + render: shell + label: Clash log + description: " +在下方附上 Clash Core 的日志,log level 使用 DEBUG +Paste the Clash core log below with the log level set to `DEBUG`. +" + - type: textarea + attributes: + label: Description + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..7404fe27c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false + +contact_links: + - name: Get help in GitHub Discussions + url: https://github.com/Dreamacro/clash/discussions + about: Have a question? Not sure if your issue affects everyone reproducibly? The quickest way to get help is on Clash's GitHub Discussions! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index d5ddc956a..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "[Feature]" -labels: '' -assignees: '' - ---- - - -感谢你向 Clash Core 提交 Feature Request! -在提交之前,请确认: - -- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求 - -请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。 - - - -我都确认过了,我要继续提交。 - ------------------------------------------------------------------- - -请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。 - - -### Clash core config - -``` -…… -``` - -### Clash log - -``` -…… -``` - -### 环境 Environment - -* Clash Core 的操作系统 (the OS that the Clash core is running on) -…… -* 使用者的操作系统 (the OS running on the client) -…… -* 网路环境或拓扑 (network conditions/topology) -…… -* iptables,如果适用 (if applicable) -…… -* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) -…… -* 其他 -…… - -### 说明 Description - - - -### 可能的解决方案 Possible Solution - - - - -### 更多信息 More Information diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..34668d1ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,36 @@ +name: Feature request +description: Suggest an idea for this project +title: "[Feature] " +body: + - type: checkboxes + id: ensure + attributes: + label: Verify steps + description: " +在提交之前,请确认 +Please verify that you've followed these steps +" + options: + - label: " +我已经在 [Issue Tracker](……/) 中找过我要提出的请求 +I have searched on the [issue tracker](……/) for a related feature request. +" + required: true + - label: " +我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题 +I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue. +" + required: true + - type: textarea + attributes: + label: Description + description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽? + validations: + required: true + - type: textarea + attributes: + label: Possible Solution + description: " +此项非必须,但是如果你有想法的话欢迎提出。 +Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change +" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 002a9cb35..35083e518 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -52,7 +52,7 @@ jobs: - name: Get all docker tags if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v3 + uses: actions/github-script@v4 id: tags with: script: | diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 000000000..26d318c30 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,12 @@ +name: Linter +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: latest + args: --disable-all -E govet -E gofumpt -E megacheck ./... diff --git a/.github/workflows/go.yml b/.github/workflows/release.yml similarity index 65% rename from .github/workflows/go.yml rename to .github/workflows/release.yml index a52be1e9a..d4bad8a37 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,18 @@ -name: Go -on: [push, pull_request] +name: Release +on: [push] jobs: - build: - name: Build runs-on: ubuntu-latest steps: + - name: Get latest go version + id: version + run: | + echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') + - name: Setup Go uses: actions/setup-go@v2 with: - go-version: 1.16 + go-version: ${{ steps.version.outputs.go_version }} - name: Check out code into the Go module directory uses: actions/checkout@v2 @@ -22,12 +25,9 @@ jobs: restore-keys: | ${{ runner.os }}-go- - - name: Get dependencies, run test and static check + - name: Get dependencies, run test run: | go test ./... - go vet ./... - go install honnef.co/go/tools/cmd/staticcheck@latest - staticcheck -- $(go list ./...) - name: Build if: startsWith(github.ref, 'refs/tags/') @@ -39,8 +39,6 @@ jobs: - name: Upload Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: bin/* draft: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7581fc403..68f986ffc 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,9 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v4 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' days-before-stale: 60 days-before-close: 5 diff --git a/.gitignore b/.gitignore index 0593cfd05..52efcc9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ bin/* # Output of the go coverage tool, specifically when used with LiteIDE *.out -# dep +# go mod vendor vendor # GoLand @@ -20,3 +20,6 @@ vendor # macOS file .DS_Store + +# test suite +test/config/cache* diff --git a/Makefile b/Makefile index 686514460..b6a5b22af 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ PLATFORM_LIST = \ WINDOWS_ARCH_LIST = \ windows-386 \ windows-amd64 \ + windows-arm64 \ windows-arm32v7 all: linux-amd64 darwin-amd64 windows-amd64 # Most used @@ -91,7 +92,10 @@ windows-386: windows-amd64: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe - + +windows-arm64: + GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + windows-arm32v7: GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe @@ -108,5 +112,9 @@ $(zip_releases): %.zip : % all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) releases: $(gz_releases) $(zip_releases) + +lint: + golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... + clean: rm $(BINDIR)/* diff --git a/README.md b/README.md index f2dbf7228..c0d7c7f72 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,13 @@ + + + +

## Features @@ -22,7 +26,7 @@ - Local HTTP/HTTPS/SOCKS server with authentication support - VMess, Shadowsocks, Trojan, Snell protocol support for remote connections - Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP. -- Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes +- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency - Remote providers, allowing users to get node lists remotely instead of hardcoding in config - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. @@ -40,20 +44,17 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash ## Premium Release [Release](https://github.com/Dreamacro/clash/releases/tag/premium) +## Development +If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library) + ## Credits * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) +* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go) ## License This software is released under the GPL-3.0 license. [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) - -## TODO - -- [x] Complementing the necessary rule operators -- [x] Redir proxy -- [x] UDP support -- [x] Connection manager diff --git a/adapters/outbound/base.go b/adapter/adapter.go similarity index 59% rename from adapters/outbound/base.go rename to adapter/adapter.go index 6e16bbaed..76bbe2e3d 100644 --- a/adapters/outbound/base.go +++ b/adapter/adapter.go @@ -1,110 +1,21 @@ -package outbound +package adapter import ( "context" "encoding/json" - "errors" + "fmt" "net" "net/http" + "net/url" "time" "github.com/Dreamacro/clash/common/queue" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "go.uber.org/atomic" ) -type Base struct { - name string - addr string - tp C.AdapterType - udp bool -} - -// Name implements C.ProxyAdapter -func (b *Base) Name() string { - return b.name -} - -// Type implements C.ProxyAdapter -func (b *Base) Type() C.AdapterType { - return b.tp -} - -// StreamConn implements C.ProxyAdapter -func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { - return c, errors.New("no support") -} - -// DialUDP implements C.ProxyAdapter -func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - return nil, errors.New("no support") -} - -// SupportUDP implements C.ProxyAdapter -func (b *Base) SupportUDP() bool { - return b.udp -} - -// MarshalJSON implements C.ProxyAdapter -func (b *Base) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": b.Type().String(), - }) -} - -// Addr implements C.ProxyAdapter -func (b *Base) Addr() string { - return b.addr -} - -// Unwrap implements C.ProxyAdapter -func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy { - return nil -} - -func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base { - return &Base{name, addr, tp, udp} -} - -type conn struct { - net.Conn - chain C.Chain -} - -// Chains implements C.Connection -func (c *conn) Chains() C.Chain { - return c.chain -} - -// AppendToChains implements C.Connection -func (c *conn) AppendToChains(a C.ProxyAdapter) { - c.chain = append(c.chain, a.Name()) -} - -func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { - return &conn{c, []string{a.Name()}} -} - -type packetConn struct { - net.PacketConn - chain C.Chain -} - -// Chains implements C.Connection -func (c *packetConn) Chains() C.Chain { - return c.chain -} - -// AppendToChains implements C.Connection -func (c *packetConn) AppendToChains(a C.ProxyAdapter) { - c.chain = append(c.chain, a.Name()) -} - -func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { - return &packetConn{pc, []string{a.Name()}} -} - type Proxy struct { C.ProxyAdapter history *queue.Queue @@ -118,20 +29,32 @@ func (p *Proxy) Alive() bool { // Dial implements C.Proxy func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { - ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) defer cancel() return p.DialContext(ctx, metadata) } // DialContext implements C.ProxyAdapter -func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - conn, err := p.ProxyAdapter.DialContext(ctx, metadata) - if err != nil { - p.alive.Store(false) - } +func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) + p.alive.Store(err == nil) return conn, err } +// DialUDP implements C.ProxyAdapter +func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) + defer cancel() + return p.ListenPacketContext(ctx, metadata) +} + +// ListenPacketContext implements C.ProxyAdapter +func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) + p.alive.Store(err == nil) + return pc, err +} + // DelayHistory implements C.Proxy func (p *Proxy) DelayHistory() []C.DelayHistory { queue := p.history.Copy() @@ -172,6 +95,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { json.Unmarshal(inner, &mapping) mapping["history"] = p.DelayHistory() mapping["name"] = p.Name() + mapping["udp"] = p.SupportUDP() return json.Marshal(mapping) } @@ -225,6 +149,8 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { return http.ErrUseLastResponse }, } + defer client.CloseIdleConnections() + resp, err := client.Do(req) if err != nil { return @@ -237,3 +163,31 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { func NewProxy(adapter C.ProxyAdapter) *Proxy { return &Proxy{adapter, queue.New(10), atomic.NewBool(true)} } + +func urlToMetadata(rawURL string) (addr C.Metadata, err error) { + u, err := url.Parse(rawURL) + if err != nil { + return + } + + port := u.Port() + if port == "" { + switch u.Scheme { + case "https": + port = "443" + case "http": + port = "80" + default: + err = fmt.Errorf("%s scheme not Support", rawURL) + return + } + } + + addr = C.Metadata{ + AddrType: C.AtypDomainName, + Host: u.Hostname(), + DstIP: nil, + DstPort: port, + } + return +} diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go new file mode 100644 index 000000000..89960cf34 --- /dev/null +++ b/adapter/inbound/http.go @@ -0,0 +1,21 @@ +package inbound + +import ( + "net" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" + "github.com/Dreamacro/clash/transport/socks5" +) + +// NewHTTP receive normal http request and return HTTPContext +func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext { + metadata := parseSocksAddr(target) + metadata.NetWork = C.TCP + metadata.Type = C.HTTP + if ip, port, err := parseAddr(source.String()); err == nil { + metadata.SrcIP = ip + metadata.SrcPort = port + } + return context.NewConnContext(conn, metadata) +} diff --git a/adapters/inbound/https.go b/adapter/inbound/https.go similarity index 100% rename from adapters/inbound/https.go rename to adapter/inbound/https.go diff --git a/adapters/inbound/packet.go b/adapter/inbound/packet.go similarity index 93% rename from adapters/inbound/packet.go rename to adapter/inbound/packet.go index 001a579b8..80b136cd6 100644 --- a/adapters/inbound/packet.go +++ b/adapter/inbound/packet.go @@ -1,8 +1,8 @@ package inbound import ( - "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" ) // PacketAdapter is a UDP Packet adapter for socks/redir/tun diff --git a/adapters/inbound/socket.go b/adapter/inbound/socket.go similarity index 91% rename from adapters/inbound/socket.go rename to adapter/inbound/socket.go index be772b7bc..be717017e 100644 --- a/adapters/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -3,9 +3,9 @@ package inbound import ( "net" - "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/context" + "github.com/Dreamacro/clash/transport/socks5" ) // NewSocket receive TCP inbound and return ConnContext diff --git a/adapters/inbound/util.go b/adapter/inbound/util.go similarity index 97% rename from adapters/inbound/util.go rename to adapter/inbound/util.go index 6241ac803..07577ec0e 100644 --- a/adapters/inbound/util.go +++ b/adapter/inbound/util.go @@ -6,8 +6,8 @@ import ( "strconv" "strings" - "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" ) func parseSocksAddr(target socks5.Addr) *C.Metadata { diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go new file mode 100644 index 000000000..e9119415e --- /dev/null +++ b/adapter/outbound/base.go @@ -0,0 +1,138 @@ +package outbound + +import ( + "context" + "encoding/json" + "errors" + "net" + + "github.com/Dreamacro/clash/component/dialer" + C "github.com/Dreamacro/clash/constant" +) + +type Base struct { + name string + addr string + iface string + tp C.AdapterType + udp bool + rmark int +} + +// Name implements C.ProxyAdapter +func (b *Base) Name() string { + return b.name +} + +// Type implements C.ProxyAdapter +func (b *Base) Type() C.AdapterType { + return b.tp +} + +// StreamConn implements C.ProxyAdapter +func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + return c, errors.New("no support") +} + +// ListenPacketContext implements C.ProxyAdapter +func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + return nil, errors.New("no support") +} + +// SupportUDP implements C.ProxyAdapter +func (b *Base) SupportUDP() bool { + return b.udp +} + +// MarshalJSON implements C.ProxyAdapter +func (b *Base) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": b.Type().String(), + }) +} + +// Addr implements C.ProxyAdapter +func (b *Base) Addr() string { + return b.addr +} + +// Unwrap implements C.ProxyAdapter +func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy { + return nil +} + +// DialOptions return []dialer.Option from struct +func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option { + if b.iface != "" { + opts = append(opts, dialer.WithInterface(b.iface)) + } + + if b.rmark != 0 { + opts = append(opts, dialer.WithRoutingMark(b.rmark)) + } + + return opts +} + +type BasicOption struct { + Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` + RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` +} + +type BaseOption struct { + Name string + Addr string + Type C.AdapterType + UDP bool + Interface string + RoutingMark int +} + +func NewBase(opt BaseOption) *Base { + return &Base{ + name: opt.Name, + addr: opt.Addr, + tp: opt.Type, + udp: opt.UDP, + iface: opt.Interface, + rmark: opt.RoutingMark, + } +} + +type conn struct { + net.Conn + chain C.Chain +} + +// Chains implements C.Connection +func (c *conn) Chains() C.Chain { + return c.chain +} + +// AppendToChains implements C.Connection +func (c *conn) AppendToChains(a C.ProxyAdapter) { + c.chain = append(c.chain, a.Name()) +} + +func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { + return &conn{c, []string{a.Name()}} +} + +type packetConn struct { + net.PacketConn + chain C.Chain +} + +// Chains implements C.Connection +func (c *packetConn) Chains() C.Chain { + return c.chain +} + +// AppendToChains implements C.Connection +func (c *packetConn) AppendToChains(a C.ProxyAdapter) { + c.chain = append(c.chain, a.Name()) +} + +func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { + return &packetConn{pc, []string{a.Name()}} +} diff --git a/adapters/outbound/direct.go b/adapter/outbound/direct.go similarity index 60% rename from adapters/outbound/direct.go rename to adapter/outbound/direct.go index 5b4a710fd..4c4305f57 100644 --- a/adapters/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -13,10 +13,8 @@ type Direct struct { } // DialContext implements C.ProxyAdapter -func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - address := net.JoinHostPort(metadata.String(), metadata.DstPort) - - c, err := dialer.DialContext(ctx, "tcp", address) +func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) if err != nil { return nil, err } @@ -24,9 +22,9 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return NewConn(c, d), nil } -// DialUDP implements C.ProxyAdapter -func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket("udp", "") +// ListenPacketContext implements C.ProxyAdapter +func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...) if err != nil { return nil, err } diff --git a/adapters/outbound/http.go b/adapter/outbound/http.go similarity index 90% rename from adapters/outbound/http.go rename to adapter/outbound/http.go index 43ca12042..7f480ce6b 100644 --- a/adapters/outbound/http.go +++ b/adapter/outbound/http.go @@ -25,6 +25,7 @@ type Http struct { } type HttpOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -53,8 +54,8 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } // DialContext implements C.ProxyAdapter -func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", h.addr) +func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } @@ -125,16 +126,16 @@ func NewHttp(option HttpOption) *Http { } tlsConfig = &tls.Config{ InsecureSkipVerify: option.SkipCertVerify, - ClientSessionCache: getClientSessionCache(), ServerName: sni, } } return &Http{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Http, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Http, + iface: option.Interface, }, user: option.UserName, pass: option.Password, diff --git a/adapters/outbound/reject.go b/adapter/outbound/reject.go similarity index 82% rename from adapters/outbound/reject.go rename to adapter/outbound/reject.go index 367504964..f1940d39a 100644 --- a/adapters/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -7,6 +7,7 @@ import ( "net" "time" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" ) @@ -15,12 +16,12 @@ type Reject struct { } // DialContext implements C.ProxyAdapter -func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { +func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { return NewConn(&NopConn{}, r), nil } -// DialUDP implements C.ProxyAdapter -func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { +// ListenPacketContext implements C.ProxyAdapter +func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { return nil, errors.New("match reject rule") } diff --git a/adapters/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go similarity index 87% rename from adapters/outbound/shadowsocks.go rename to adapter/outbound/shadowsocks.go index edcd60abe..5e5ebd7c4 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -2,7 +2,6 @@ package outbound import ( "context" - "encoding/json" "errors" "fmt" "net" @@ -10,10 +9,10 @@ import ( "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" - obfs "github.com/Dreamacro/clash/component/simple-obfs" - "github.com/Dreamacro/clash/component/socks5" - v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" C "github.com/Dreamacro/clash/constant" + obfs "github.com/Dreamacro/clash/transport/simple-obfs" + "github.com/Dreamacro/clash/transport/socks5" + v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" "github.com/Dreamacro/go-shadowsocks2/core" ) @@ -29,6 +28,7 @@ type ShadowSocks struct { } type ShadowSocksOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -75,8 +75,8 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e } // DialContext implements C.ProxyAdapter -func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ss.addr) +func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } @@ -88,9 +88,9 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ return NewConn(c, ss), err } -// DialUDP implements C.ProxyAdapter -func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket("udp", "") +// ListenPacketContext implements C.ProxyAdapter +func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) if err != nil { return nil, err } @@ -105,13 +105,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil } -// MarshalJSON implements C.ProxyAdapter -func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": ss.Type().String(), - }) -} - func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) cipher := option.Cipher @@ -157,16 +150,16 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { if opts.TLS { v2rayOption.TLS = true v2rayOption.SkipCertVerify = opts.SkipCertVerify - v2rayOption.SessionCache = getClientSessionCache() } } return &ShadowSocks{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Shadowsocks, - udp: option.UDP, + name: option.Name, + addr: addr, + tp: C.Shadowsocks, + udp: option.UDP, + iface: option.Interface, }, cipher: ciph, diff --git a/adapters/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go similarity index 83% rename from adapters/outbound/shadowsocksr.go rename to adapter/outbound/shadowsocksr.go index 6636d5ff9..d244df406 100644 --- a/adapters/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -2,15 +2,14 @@ package outbound import ( "context" - "encoding/json" "fmt" "net" "strconv" "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/ssr/obfs" - "github.com/Dreamacro/clash/component/ssr/protocol" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/ssr/obfs" + "github.com/Dreamacro/clash/transport/ssr/protocol" "github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/shadowaead" @@ -25,6 +24,7 @@ type ShadowSocksR struct { } type ShadowSocksROption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -60,8 +60,8 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, } // DialContext implements C.ProxyAdapter -func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ssr.addr) +func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) } @@ -73,9 +73,9 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) return NewConn(c, ssr), err } -// DialUDP implements C.ProxyAdapter -func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := dialer.ListenPacket("udp", "") +// ListenPacketContext implements C.ProxyAdapter +func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := dialer.ListenPacket(ctx, "udp", "", ssr.Base.DialOptions(opts...)...) if err != nil { return nil, err } @@ -91,13 +91,6 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil } -// MarshalJSON implements C.ProxyAdapter -func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": ssr.Type().String(), - }) -} - func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) cipher := option.Cipher @@ -144,10 +137,11 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { return &ShadowSocksR{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.ShadowsocksR, - udp: option.UDP, + name: option.Name, + addr: addr, + tp: C.ShadowsocksR, + udp: option.UDP, + iface: option.Interface, }, cipher: coreCiph, obfs: obfs, diff --git a/adapters/outbound/snell.go b/adapter/outbound/snell.go similarity index 84% rename from adapters/outbound/snell.go rename to adapter/outbound/snell.go index 0b65b272b..d0a26ecc1 100644 --- a/adapters/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -8,9 +8,9 @@ import ( "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" - obfs "github.com/Dreamacro/clash/component/simple-obfs" - "github.com/Dreamacro/clash/component/snell" C "github.com/Dreamacro/clash/constant" + obfs "github.com/Dreamacro/clash/transport/simple-obfs" + "github.com/Dreamacro/clash/transport/snell" ) type Snell struct { @@ -22,6 +22,7 @@ type Snell struct { } type SnellOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -51,20 +52,20 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell { // StreamConn implements C.ProxyAdapter func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) - port, _ := strconv.Atoi(metadata.DstPort) + port, _ := strconv.ParseInt(metadata.DstPort, 10, 16) err := snell.WriteHeader(c, metadata.String(), uint(port), s.version) return c, err } // DialContext implements C.ProxyAdapter -func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - if s.version == snell.Version2 { +func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + if s.version == snell.Version2 && len(opts) == 0 { c, err := s.pool.Get() if err != nil { return nil, err } - port, _ := strconv.Atoi(metadata.DstPort) + port, _ := strconv.ParseUint(metadata.DstPort, 10, 16) if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil { c.Close() return nil, err @@ -72,7 +73,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn return NewConn(c, s), err } - c, err := dialer.DialContext(ctx, "tcp", s.addr) + c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.addr, err) } @@ -111,9 +112,10 @@ func NewSnell(option SnellOption) (*Snell, error) { s := &Snell{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Snell, + name: option.Name, + addr: addr, + tp: C.Snell, + iface: option.Interface, }, psk: psk, obfsOption: obfsOption, @@ -122,7 +124,7 @@ func NewSnell(option SnellOption) (*Snell, error) { if option.Version == snell.Version2 { s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) { - c, err := dialer.DialContext(ctx, "tcp", addr) + c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...) if err != nil { return nil, err } diff --git a/adapters/outbound/socks5.go b/adapter/outbound/socks5.go similarity index 84% rename from adapters/outbound/socks5.go rename to adapter/outbound/socks5.go index 377d7d173..d81c76140 100644 --- a/adapters/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -6,13 +6,12 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "strconv" "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" ) type Socks5 struct { @@ -25,6 +24,7 @@ type Socks5 struct { } type Socks5Option struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -60,8 +60,8 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) } // DialContext implements C.ProxyAdapter -func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { - c, err := dialer.DialContext(ctx, "tcp", ss.addr) +func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } @@ -77,11 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Co return NewConn(c, ss), nil } -// DialUDP implements C.ProxyAdapter -func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { - ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) - defer cancel() - c, err := dialer.DialContext(ctx, "tcp", ss.addr) +// ListenPacketContext implements C.ProxyAdapter +func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) if err != nil { err = fmt.Errorf("%s connect error: %w", ss.addr, err) return @@ -110,13 +108,13 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { return } - pc, err := dialer.ListenPacket("udp", "") + pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) if err != nil { return } go func() { - io.Copy(ioutil.Discard, c) + io.Copy(io.Discard, c) c.Close() // A UDP association terminates when the TCP connection that the UDP // ASSOCIATE request arrived on terminates. RFC1928 @@ -145,17 +143,17 @@ func NewSocks5(option Socks5Option) *Socks5 { if option.TLS { tlsConfig = &tls.Config{ InsecureSkipVerify: option.SkipCertVerify, - ClientSessionCache: getClientSessionCache(), ServerName: option.Server, } } return &Socks5{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Socks5, - udp: option.UDP, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Socks5, + udp: option.UDP, + iface: option.Interface, }, user: option.UserName, pass: option.Password, diff --git a/adapters/outbound/trojan.go b/adapter/outbound/trojan.go similarity index 69% rename from adapters/outbound/trojan.go rename to adapter/outbound/trojan.go index cfd734499..35dbea1e0 100644 --- a/adapters/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -3,15 +3,15 @@ package outbound import ( "context" "crypto/tls" - "encoding/json" "fmt" "net" + "net/http" "strconv" "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/gun" - "github.com/Dreamacro/clash/component/trojan" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/gun" + "github.com/Dreamacro/clash/transport/trojan" "golang.org/x/net/http2" ) @@ -19,6 +19,7 @@ import ( type Trojan struct { *Base instance *trojan.Trojan + option *TrojanOption // for gun mux gunTLSConfig *tls.Config @@ -27,6 +28,7 @@ type Trojan struct { } type TrojanOption struct { + BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` @@ -37,6 +39,34 @@ type TrojanOption struct { UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSOpts WSOptions `proxy:"ws-opts,omitempty"` +} + +func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { + if t.option.Network == "ws" { + host, port, _ := net.SplitHostPort(t.addr) + wsOpts := &trojan.WebsocketOption{ + Host: host, + Port: port, + Path: t.option.WSOpts.Path, + } + + if t.option.SNI != "" { + wsOpts.Host = t.option.SNI + } + + if len(t.option.WSOpts.Headers) != 0 { + header := http.Header{} + for key, value := range t.option.WSOpts.Headers { + header.Add(key, value) + } + wsOpts.Headers = header + } + + return t.instance.StreamWebsocketConn(c, wsOpts) + } + + return t.instance.StreamConn(c) } // StreamConn implements C.ProxyAdapter @@ -45,7 +75,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) if t.transport != nil { c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig) } else { - c, err = t.instance.StreamConn(c) + c, err = t.plainStream(c) } if err != nil { @@ -57,9 +87,9 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) } // DialContext implements C.ProxyAdapter -func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { +func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { // gun transport - if t.transport != nil { + if t.transport != nil && len(opts) == 0 { c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, err @@ -73,7 +103,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con return NewConn(c, t), nil } - c, err := dialer.DialContext(ctx, "tcp", t.addr) + c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } @@ -89,27 +119,25 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con return NewConn(c, t), err } -// DialUDP implements C.ProxyAdapter -func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { +// ListenPacketContext implements C.ProxyAdapter +func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { var c net.Conn // grpc transport - if t.transport != nil { + if t.transport != nil && len(opts) == 0 { c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } defer safeConnClose(c, err) } else { - ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) - defer cancel() - c, err = dialer.DialContext(ctx, "tcp", t.addr) + c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } defer safeConnClose(c, err) tcpKeepAlive(c) - c, err = t.instance.StreamConn(c) + c, err = t.plainStream(c) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } @@ -124,21 +152,14 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { return newPacketConn(pc, t), err } -func (t *Trojan) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": t.Type().String(), - }) -} - 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, - ClientSessionCache: getClientSessionCache(), + Password: option.Password, + ALPN: option.ALPN, + ServerName: option.Server, + SkipCertVerify: option.SkipCertVerify, } if option.SNI != "" { @@ -147,17 +168,19 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { t := &Trojan{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Trojan, - udp: option.UDP, + name: option.Name, + addr: addr, + tp: C.Trojan, + udp: option.UDP, + iface: option.Interface, }, instance: trojan.New(tOption), + option: &option, } if option.Network == "grpc" { dialFn := func(network, addr string) (net.Conn, error) { - c, err := dialer.DialContext(context.Background(), "tcp", t.addr) + c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) } @@ -170,7 +193,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { MinVersion: tls.VersionTLS12, InsecureSkipVerify: tOption.SkipCertVerify, ServerName: tOption.ServerName, - ClientSessionCache: getClientSessionCache(), } t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) diff --git a/adapters/outbound/util.go b/adapter/outbound/util.go similarity index 58% rename from adapters/outbound/util.go rename to adapter/outbound/util.go index 932f741cc..b376522fa 100644 --- a/adapters/outbound/util.go +++ b/adapter/outbound/util.go @@ -2,56 +2,15 @@ package outbound import ( "bytes" - "crypto/tls" - "fmt" "net" - "net/url" "strconv" - "sync" "time" "github.com/Dreamacro/clash/component/resolver" - "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" ) -const ( - tcpTimeout = 5 * time.Second -) - -var ( - globalClientSessionCache tls.ClientSessionCache - once sync.Once -) - -func urlToMetadata(rawURL string) (addr C.Metadata, err error) { - u, err := url.Parse(rawURL) - if err != nil { - return - } - - port := u.Port() - if port == "" { - switch u.Scheme { - case "https": - port = "443" - case "http": - port = "80" - default: - err = fmt.Errorf("%s scheme not Support", rawURL) - return - } - } - - addr = C.Metadata{ - AddrType: C.AtypDomainName, - Host: u.Hostname(), - DstIP: nil, - DstPort: port, - } - return -} - func tcpKeepAlive(c net.Conn) { if tcp, ok := c.(*net.TCPConn); ok { tcp.SetKeepAlive(true) @@ -59,17 +18,10 @@ func tcpKeepAlive(c net.Conn) { } } -func getClientSessionCache() tls.ClientSessionCache { - once.Do(func() { - globalClientSessionCache = tls.NewLRUClientSessionCache(128) - }) - return globalClientSessionCache -} - func serializesSocksAddr(metadata *C.Metadata) []byte { var buf [][]byte aType := uint8(metadata.AddrType) - p, _ := strconv.Atoi(metadata.DstPort) + p, _ := strconv.ParseUint(metadata.DstPort, 10, 16) port := []byte{uint8(p >> 8), uint8(p & 0xff)} switch metadata.AddrType { case socks5.AtypDomainName: diff --git a/adapters/outbound/vmess.go b/adapter/outbound/vmess.go similarity index 70% rename from adapters/outbound/vmess.go rename to adapter/outbound/vmess.go index 528db7f5c..99ea531ca 100644 --- a/adapters/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -11,10 +11,10 @@ import ( "strings" "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/gun" "github.com/Dreamacro/clash/component/resolver" - "github.com/Dreamacro/clash/component/vmess" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/gun" + "github.com/Dreamacro/clash/transport/vmess" "golang.org/x/net/http2" ) @@ -31,22 +31,26 @@ type Vmess struct { } type VmessOption struct { - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - UUID string `proxy:"uuid"` - AlterID int `proxy:"alterId"` - Cipher string `proxy:"cipher"` - 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"` + BasicOption + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + AlterID int `proxy:"alterId"` + Cipher string `proxy:"cipher"` + UDP bool `proxy:"udp,omitempty"` + Network string `proxy:"network,omitempty"` + TLS bool `proxy:"tls,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + ServerName string `proxy:"servername,omitempty"` + HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` + GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSOpts WSOptions `proxy:"ws-opts,omitempty"` + + // TODO: remove these until 2022 + WSHeaders map[string]string `proxy:"ws-headers,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` } type HTTPOptions struct { @@ -64,21 +68,37 @@ type GrpcOptions struct { GrpcServiceName string `proxy:"grpc-service-name,omitempty"` } +type WSOptions struct { + Path string `proxy:"path,omitempty"` + Headers map[string]string `proxy:"headers,omitempty"` + MaxEarlyData int `proxy:"max-early-data,omitempty"` + EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` +} + // StreamConn implements C.ProxyAdapter func (v *Vmess) 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 v.option.WSOpts.Path == "" { + v.option.WSOpts.Path = v.option.WSPath + } + if len(v.option.WSOpts.Headers) == 0 { + v.option.WSOpts.Headers = v.option.WSHeaders } - if len(v.option.WSHeaders) != 0 { + host, port, _ := net.SplitHostPort(v.addr) + wsOpts := &vmess.WebsocketConfig{ + Host: host, + Port: port, + Path: v.option.WSOpts.Path, + MaxEarlyData: v.option.WSOpts.MaxEarlyData, + EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + } + + if len(v.option.WSOpts.Headers) != 0 { header := http.Header{} - for key, value := range v.option.WSHeaders { + for key, value := range v.option.WSOpts.Headers { header.Add(key, value) } wsOpts.Headers = header @@ -86,9 +106,16 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { if v.option.TLS { wsOpts.TLS = true - wsOpts.SessionCache = getClientSessionCache() - wsOpts.SkipCertVerify = v.option.SkipCertVerify - wsOpts.ServerName = v.option.ServerName + wsOpts.TLSConfig = &tls.Config{ + ServerName: host, + InsecureSkipVerify: v.option.SkipCertVerify, + NextProtos: []string{"http/1.1"}, + } + if v.option.ServerName != "" { + wsOpts.TLSConfig.ServerName = v.option.ServerName + } else if host := wsOpts.Headers.Get("Host"); host != "" { + wsOpts.TLSConfig.ServerName = host + } } c, err = vmess.StreamWebsocketConn(c, wsOpts) case "http": @@ -98,7 +125,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { tlsOpts := &vmess.TLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, - SessionCache: getClientSessionCache(), } if v.option.ServerName != "" { @@ -125,7 +151,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { tlsOpts := vmess.TLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, - SessionCache: getClientSessionCache(), NextProtos: []string{"h2"}, } @@ -153,7 +178,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { tlsOpts := &vmess.TLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, - SessionCache: getClientSessionCache(), } if v.option.ServerName != "" { @@ -172,9 +196,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } // DialContext implements C.ProxyAdapter -func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { +func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { // gun transport - if v.transport != nil { + if v.transport != nil && len(opts) == 0 { c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -189,7 +213,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn return NewConn(c, v), nil } - c, err := dialer.DialContext(ctx, "tcp", v.addr) + c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } @@ -200,8 +224,8 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn return NewConn(c, v), err } -// DialUDP implements C.ProxyAdapter -func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { +// ListenPacketContext implements C.ProxyAdapter +func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ 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) @@ -213,7 +237,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { var c net.Conn // gun transport - if v.transport != nil { + if v.transport != nil && len(opts) == 0 { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -222,9 +246,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { c, err = v.client.StreamConn(c, parseVmessAddr(metadata)) } else { - ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) - defer cancel() - c, err = dialer.DialContext(ctx, "tcp", v.addr) + c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } @@ -264,10 +286,11 @@ func NewVmess(option VmessOption) (*Vmess, error) { v := &Vmess{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Vmess, - udp: option.UDP, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Vmess, + udp: option.UDP, + iface: option.Interface, }, client: client, option: &option, @@ -280,7 +303,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { } case "grpc": dialFn := func(network, addr string) (net.Conn, error) { - c, err := dialer.DialContext(context.Background(), "tcp", v.addr) + c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...) if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } @@ -330,7 +353,7 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { copy(addr[1:], []byte(metadata.Host)) } - port, _ := strconv.Atoi(metadata.DstPort) + port, _ := strconv.ParseUint(metadata.DstPort, 10, 16) return &vmess.DstAddr{ UDP: metadata.NetWork == C.UDP, AddrType: addrType, diff --git a/adapters/outboundgroup/common.go b/adapter/outboundgroup/common.go similarity index 90% rename from adapters/outboundgroup/common.go rename to adapter/outboundgroup/common.go index c5c719f29..758c7801a 100644 --- a/adapters/outboundgroup/common.go +++ b/adapter/outboundgroup/common.go @@ -3,8 +3,8 @@ package outboundgroup import ( "time" - "github.com/Dreamacro/clash/adapters/provider" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" ) const ( diff --git a/adapters/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go similarity index 68% rename from adapters/outboundgroup/fallback.go rename to adapter/outboundgroup/fallback.go index b3dbe0cb7..b08af487f 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -4,10 +4,11 @@ import ( "context" "encoding/json" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" ) type Fallback struct { @@ -23,19 +24,19 @@ func (f *Fallback) Now() string { } // DialContext implements C.ProxyAdapter -func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { +func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { proxy := f.findAliveProxy(true) - c, err := proxy.DialContext(ctx, metadata) + c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(f) } return c, err } -// DialUDP implements C.ProxyAdapter -func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { +// ListenPacketContext implements C.ProxyAdapter +func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { proxy := f.findAliveProxy(true) - pc, err := proxy.DialUDP(metadata) + pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...) if err == nil { pc.AppendToChains(f) } @@ -90,11 +91,16 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy { return proxies[0] } -func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { +func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { return &Fallback{ - Base: outbound.NewBase(options.Name, "", C.Fallback, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.Fallback, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, - disableUDP: options.DisableUDP, + disableUDP: option.DisableUDP, } } diff --git a/adapters/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go similarity index 79% rename from adapters/outboundgroup/loadbalance.go rename to adapter/outboundgroup/loadbalance.go index b44fade17..26c8052ad 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -7,11 +7,12 @@ import ( "fmt" "net" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" "golang.org/x/net/publicsuffix" ) @@ -69,7 +70,7 @@ func jumpHash(key uint64, buckets int32) int32 { } // DialContext implements C.ProxyAdapter -func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { +func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { defer func() { if err == nil { c.AppendToChains(lb) @@ -78,12 +79,12 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c proxy := lb.Unwrap(metadata) - c, err = proxy.DialContext(ctx, metadata) + c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...) return } -// DialUDP implements C.ProxyAdapter -func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { +// ListenPacketContext implements C.ProxyAdapter +func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) { defer func() { if err == nil { pc.AppendToChains(lb) @@ -91,8 +92,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error }() proxy := lb.Unwrap(metadata) - - return proxy.DialUDP(metadata) + return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...) } // SupportUDP implements C.ProxyAdapter @@ -159,7 +159,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { }) } -func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { +func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { var strategyFn strategyFn switch strategy { case "consistent-hashing": @@ -170,10 +170,15 @@ func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvid return nil, fmt.Errorf("%w: %s", errStrategy, strategy) } return &LoadBalance{ - Base: outbound.NewBase(options.Name, "", C.LoadBalance, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.LoadBalance, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, strategyFn: strategyFn, - disableUDP: options.DisableUDP, + disableUDP: option.DisableUDP, }, nil } diff --git a/adapters/outboundgroup/parser.go b/adapter/outboundgroup/parser.go similarity index 89% rename from adapters/outboundgroup/parser.go rename to adapter/outboundgroup/parser.go index 82ceaa798..190d333de 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -4,9 +4,11 @@ import ( "errors" "fmt" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/outbound" + "github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" + types "github.com/Dreamacro/clash/constant/provider" ) var ( @@ -18,6 +20,7 @@ var ( ) type GroupCommonOption struct { + outbound.BasicOption Name string `group:"name"` Type string `group:"type"` Proxies []string `group:"proxies,omitempty"` @@ -28,7 +31,7 @@ type GroupCommonOption struct { DisableUDP bool `group:"disable-udp,omitempty"` } -func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) { +func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) groupOption := &GroupCommonOption{ @@ -44,7 +47,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, groupName := groupOption.Name - providers := []provider.ProxyProvider{} + providers := []types.ProxyProvider{} if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { return nil, errMissProxy @@ -138,15 +141,15 @@ func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { return ps, nil } -func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) { - var ps []provider.ProxyProvider +func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) { + var ps []types.ProxyProvider for _, name := range list { p, ok := mapping[name] if !ok { return nil, fmt.Errorf("'%s' not found", name) } - if p.VehicleType() == provider.Compatible { + if p.VehicleType() == types.Compatible { return nil, fmt.Errorf("proxy group %s can't contains in `use`", name) } ps = append(ps, p) diff --git a/adapters/outboundgroup/relay.go b/adapter/outboundgroup/relay.go similarity index 69% rename from adapters/outboundgroup/relay.go rename to adapter/outboundgroup/relay.go index 6583d3903..393a69bb9 100644 --- a/adapters/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -3,14 +3,13 @@ package outboundgroup import ( "context" "encoding/json" - "errors" "fmt" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" ) type Relay struct { @@ -20,15 +19,25 @@ type Relay struct { } // DialContext implements C.ProxyAdapter -func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - proxies := r.proxies(metadata, true) - if len(proxies) == 0 { - return nil, errors.New("proxy does not exist") +func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + var proxies []C.Proxy + for _, proxy := range r.proxies(metadata, true) { + if proxy.Type() != C.Direct { + proxies = append(proxies, proxy) + } } + + switch len(proxies) { + case 0: + return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) + case 1: + return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) + } + first := proxies[0] last := proxies[len(proxies)-1] - c, err := dialer.DialContext(ctx, "tcp", first.Addr()) + c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) if err != nil { return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) } @@ -91,9 +100,14 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { return proxies } -func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay { +func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { return &Relay{ - Base: outbound.NewBase(options.Name, "", C.Relay, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.Relay, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, } diff --git a/adapters/outboundgroup/selector.go b/adapter/outboundgroup/selector.go similarity index 69% rename from adapters/outboundgroup/selector.go rename to adapter/outboundgroup/selector.go index 67afaaabe..47e4b87d0 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -5,10 +5,11 @@ import ( "encoding/json" "errors" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" ) type Selector struct { @@ -20,17 +21,17 @@ type Selector struct { } // DialContext implements C.ProxyAdapter -func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := s.selectedProxy(true).DialContext(ctx, metadata) +func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(s) } return c, err } -// DialUDP implements C.ProxyAdapter -func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := s.selectedProxy(true).DialUDP(metadata) +// ListenPacketContext implements C.ProxyAdapter +func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...) if err == nil { pc.AppendToChains(s) } @@ -96,13 +97,18 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy { return elm.(C.Proxy) } -func NewSelector(options *GroupCommonOption, providers []provider.ProxyProvider) *Selector { +func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { selected := providers[0].Proxies()[0].Name() return &Selector{ - Base: outbound.NewBase(options.Name, "", C.Selector, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.Selector, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, selected: selected, - disableUDP: options.DisableUDP, + disableUDP: option.DisableUDP, } } diff --git a/adapters/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go similarity index 75% rename from adapters/outboundgroup/urltest.go rename to adapter/outboundgroup/urltest.go index 52c5fef91..47144c046 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -5,10 +5,11 @@ import ( "encoding/json" "time" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" ) type urlTestOption func(*URLTest) @@ -34,17 +35,17 @@ func (u *URLTest) Now() string { } // DialContext implements C.ProxyAdapter -func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { - c, err = u.fast(true).DialContext(ctx, metadata) +func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { + c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) if err == nil { c.AppendToChains(u) } return c, err } -// DialUDP implements C.ProxyAdapter -func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := u.fast(true).DialUDP(metadata) +// ListenPacketContext implements C.ProxyAdapter +func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) if err == nil { pc.AppendToChains(u) } @@ -133,13 +134,18 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption { return opts } -func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { +func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { urlTest := &URLTest{ - Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false), + Base: outbound.NewBase(outbound.BaseOption{ + Name: option.Name, + Type: C.URLTest, + Interface: option.Interface, + RoutingMark: option.RoutingMark, + }), single: singledo.NewSingle(defaultGetProxiesDuration), fastSingle: singledo.NewSingle(time.Second * 10), providers: providers, - disableUDP: commonOptions.DisableUDP, + disableUDP: option.DisableUDP, } for _, option := range options { diff --git a/adapters/outboundgroup/util.go b/adapter/outboundgroup/util.go similarity index 100% rename from adapters/outboundgroup/util.go rename to adapter/outboundgroup/util.go diff --git a/adapters/outbound/parser.go b/adapter/parser.go similarity index 63% rename from adapters/outbound/parser.go rename to adapter/parser.go index f0f894da3..62d152259 100644 --- a/adapters/outbound/parser.go +++ b/adapter/parser.go @@ -1,8 +1,9 @@ -package outbound +package adapter import ( "fmt" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" ) @@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { ) switch proxyType { case "ss": - ssOption := &ShadowSocksOption{} + ssOption := &outbound.ShadowSocksOption{} err = decoder.Decode(mapping, ssOption) if err != nil { break } - proxy, err = NewShadowSocks(*ssOption) + proxy, err = outbound.NewShadowSocks(*ssOption) case "ssr": - ssrOption := &ShadowSocksROption{} + ssrOption := &outbound.ShadowSocksROption{} err = decoder.Decode(mapping, ssrOption) if err != nil { break } - proxy, err = NewShadowSocksR(*ssrOption) + proxy, err = outbound.NewShadowSocksR(*ssrOption) case "socks5": - socksOption := &Socks5Option{} + socksOption := &outbound.Socks5Option{} err = decoder.Decode(mapping, socksOption) if err != nil { break } - proxy = NewSocks5(*socksOption) + proxy = outbound.NewSocks5(*socksOption) case "http": - httpOption := &HttpOption{} + httpOption := &outbound.HttpOption{} err = decoder.Decode(mapping, httpOption) if err != nil { break } - proxy = NewHttp(*httpOption) + proxy = outbound.NewHttp(*httpOption) case "vmess": - vmessOption := &VmessOption{ - HTTPOpts: HTTPOptions{ + vmessOption := &outbound.VmessOption{ + HTTPOpts: outbound.HTTPOptions{ Method: "GET", Path: []string{"/"}, }, @@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { if err != nil { break } - proxy, err = NewVmess(*vmessOption) + proxy, err = outbound.NewVmess(*vmessOption) case "snell": - snellOption := &SnellOption{} + snellOption := &outbound.SnellOption{} err = decoder.Decode(mapping, snellOption) if err != nil { break } - proxy, err = NewSnell(*snellOption) + proxy, err = outbound.NewSnell(*snellOption) case "trojan": - trojanOption := &TrojanOption{} + trojanOption := &outbound.TrojanOption{} err = decoder.Decode(mapping, trojanOption) if err != nil { break } - proxy, err = NewTrojan(*trojanOption) + proxy, err = outbound.NewTrojan(*trojanOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } diff --git a/adapters/provider/fetcher.go b/adapter/provider/fetcher.go similarity index 85% rename from adapters/provider/fetcher.go rename to adapter/provider/fetcher.go index 5ef3f25ca..6c1e96b43 100644 --- a/adapters/provider/fetcher.go +++ b/adapter/provider/fetcher.go @@ -3,24 +3,24 @@ package provider import ( "bytes" "crypto/md5" - "io/ioutil" "os" "path/filepath" "time" + types "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/log" ) var ( - fileMode os.FileMode = 0666 - dirMode os.FileMode = 0755 + fileMode os.FileMode = 0o666 + dirMode os.FileMode = 0o755 ) type parser = func([]byte) (interface{}, error) type fetcher struct { name string - vehicle Vehicle + vehicle types.Vehicle updatedAt *time.Time ticker *time.Ticker done chan struct{} @@ -33,7 +33,7 @@ func (f *fetcher) Name() string { return f.name } -func (f *fetcher) VehicleType() VehicleType { +func (f *fetcher) VehicleType() types.VehicleType { return f.vehicle.Type() } @@ -44,7 +44,7 @@ func (f *fetcher) Initial() (interface{}, error) { isLocal bool ) if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { - buf, err = ioutil.ReadFile(f.vehicle.Path()) + buf, err = os.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() f.updatedAt = &modTime isLocal = true @@ -76,7 +76,7 @@ func (f *fetcher) Initial() (interface{}, error) { isLocal = false } - if f.vehicle.Type() != File && !isLocal { + if f.vehicle.Type() != types.File && !isLocal { if err := safeWrite(f.vehicle.Path(), buf); err != nil { return nil, err } @@ -110,7 +110,7 @@ func (f *fetcher) Update() (interface{}, bool, error) { return nil, false, err } - if f.vehicle.Type() != File { + if f.vehicle.Type() != types.File { if err := safeWrite(f.vehicle.Path(), buf); err != nil { return nil, false, err } @@ -164,10 +164,10 @@ func safeWrite(path string, buf []byte) error { } } - return ioutil.WriteFile(path, buf, fileMode) + return os.WriteFile(path, buf, fileMode) } -func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher { +func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(interface{})) *fetcher { var ticker *time.Ticker if interval != 0 { ticker = time.NewTicker(interval) diff --git a/adapters/provider/healthcheck.go b/adapter/provider/healthcheck.go similarity index 81% rename from adapters/provider/healthcheck.go rename to adapter/provider/healthcheck.go index 98f934e42..f41a4788d 100644 --- a/adapters/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -2,9 +2,9 @@ package provider import ( "context" - "sync" "time" + "github.com/Dreamacro/clash/common/batch" C "github.com/Dreamacro/clash/constant" "go.uber.org/atomic" @@ -59,20 +59,17 @@ func (hc *HealthCheck) touch() { } func (hc *HealthCheck) check() { - ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) - wg := &sync.WaitGroup{} - + b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10)) for _, proxy := range hc.proxies { - wg.Add(1) - - go func(p C.Proxy) { + p := proxy + b.Go(p.Name(), func() (interface{}, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) + defer cancel() p.URLTest(ctx, hc.url) - wg.Done() - }(proxy) + return nil, nil + }) } - - wg.Wait() - cancel() + b.Wait() } func (hc *HealthCheck) close() { diff --git a/adapters/provider/parser.go b/adapter/provider/parser.go similarity index 89% rename from adapters/provider/parser.go rename to adapter/provider/parser.go index aa5d7ae29..8a1739669 100644 --- a/adapters/provider/parser.go +++ b/adapter/provider/parser.go @@ -7,11 +7,10 @@ import ( "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" + types "github.com/Dreamacro/clash/constant/provider" ) -var ( - errVehicleType = errors.New("unsupport vehicle type") -) +var errVehicleType = errors.New("unsupport vehicle type") type healthCheckSchema struct { Enable bool `provider:"enable"` @@ -28,7 +27,7 @@ type proxyProviderSchema struct { HealthCheck healthCheckSchema `provider:"health-check,omitempty"` } -func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) { +func ParseProxyProvider(name string, mapping map[string]interface{}) (types.ProxyProvider, error) { decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) schema := &proxyProviderSchema{ @@ -48,7 +47,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi path := C.Path.Resolve(schema.Path) - var vehicle Vehicle + var vehicle types.Vehicle switch schema.Type { case "file": vehicle = NewFileVehicle(path) diff --git a/adapters/provider/provider.go b/adapter/provider/provider.go similarity index 77% rename from adapters/provider/provider.go rename to adapter/provider/provider.go index 756a89d12..32baaedd9 100644 --- a/adapters/provider/provider.go +++ b/adapter/provider/provider.go @@ -7,8 +7,9 @@ import ( "runtime" "time" - "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapter" C "github.com/Dreamacro/clash/constant" + types "github.com/Dreamacro/clash/constant/provider" "gopkg.in/yaml.v2" ) @@ -17,45 +18,6 @@ const ( ReservedName = "default" ) -// Provider Type -const ( - Proxy ProviderType = iota - Rule -) - -// ProviderType defined -type ProviderType int - -func (pt ProviderType) String() string { - switch pt { - case Proxy: - return "Proxy" - case Rule: - return "Rule" - default: - return "Unknown" - } -} - -// Provider interface -type Provider interface { - Name() string - VehicleType() VehicleType - Type() ProviderType - Initial() error - Update() error -} - -// ProxyProvider interface -type ProxyProvider interface { - Provider - Proxies() []C.Proxy - // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. - // Commonly used in Dial and DialUDP - ProxiesWithTouch() []C.Proxy - HealthCheck() -} - type ProxySchema struct { Proxies []map[string]interface{} `yaml:"proxies"` } @@ -107,8 +69,8 @@ func (pp *proxySetProvider) Initial() error { return nil } -func (pp *proxySetProvider) Type() ProviderType { - return Proxy +func (pp *proxySetProvider) Type() types.ProviderType { + return types.Proxy } func (pp *proxySetProvider) Proxies() []C.Proxy { @@ -133,7 +95,7 @@ func proxiesParse(buf []byte) (interface{}, error) { proxies := []C.Proxy{} for idx, mapping := range schema.Proxies { - proxy, err := outbound.ParseProxy(mapping) + proxy, err := adapter.ParseProxy(mapping) if err != nil { return nil, fmt.Errorf("proxy %d error: %w", idx, err) } @@ -160,7 +122,7 @@ func stopProxyProvider(pd *ProxySetProvider) { pd.fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { +func NewProxySetProvider(name string, interval time.Duration, vehicle types.Vehicle, hc *HealthCheck) *ProxySetProvider { if hc.auto() { go hc.process() } @@ -219,12 +181,12 @@ func (cp *compatibleProvider) Initial() error { return nil } -func (cp *compatibleProvider) VehicleType() VehicleType { - return Compatible +func (cp *compatibleProvider) VehicleType() types.VehicleType { + return types.Compatible } -func (cp *compatibleProvider) Type() ProviderType { - return Proxy +func (cp *compatibleProvider) Type() types.ProviderType { + return types.Proxy } func (cp *compatibleProvider) Proxies() []C.Proxy { @@ -242,7 +204,7 @@ func stopCompatibleProvider(pd *CompatibleProvider) { func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { if len(proxies) == 0 { - return nil, errors.New("Provider need one proxy at least") + return nil, errors.New("provider need one proxy at least") } if hc.auto() { diff --git a/adapters/provider/vehicle.go b/adapter/provider/vehicle.go similarity index 69% rename from adapters/provider/vehicle.go rename to adapter/provider/vehicle.go index 13d898fc8..4f08c317e 100644 --- a/adapters/provider/vehicle.go +++ b/adapter/provider/vehicle.go @@ -2,49 +2,23 @@ package provider import ( "context" - "io/ioutil" + "io" + "net" "net/http" "net/url" + "os" "time" "github.com/Dreamacro/clash/component/dialer" + types "github.com/Dreamacro/clash/constant/provider" ) -// Vehicle Type -const ( - File VehicleType = iota - HTTP - Compatible -) - -// VehicleType defined -type VehicleType int - -func (v VehicleType) String() string { - switch v { - case File: - return "File" - case HTTP: - return "HTTP" - case Compatible: - return "Compatible" - default: - return "Unknown" - } -} - -type Vehicle interface { - Read() ([]byte, error) - Path() string - Type() VehicleType -} - type FileVehicle struct { path string } -func (f *FileVehicle) Type() VehicleType { - return File +func (f *FileVehicle) Type() types.VehicleType { + return types.File } func (f *FileVehicle) Path() string { @@ -52,7 +26,7 @@ func (f *FileVehicle) Path() string { } func (f *FileVehicle) Read() ([]byte, error) { - return ioutil.ReadFile(f.path) + return os.ReadFile(f.path) } func NewFileVehicle(path string) *FileVehicle { @@ -64,8 +38,8 @@ type HTTPVehicle struct { path string } -func (h *HTTPVehicle) Type() VehicleType { - return HTTP +func (h *HTTPVehicle) Type() types.VehicleType { + return types.HTTP } func (h *HTTPVehicle) Path() string { @@ -99,7 +73,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - DialContext: dialer.DialContext, + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return dialer.DialContext(ctx, network, address) + }, } client := http.Client{Transport: transport} @@ -109,7 +85,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) { } defer resp.Body.Close() - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go deleted file mode 100644 index b01c62bfb..000000000 --- a/adapters/inbound/http.go +++ /dev/null @@ -1,61 +0,0 @@ -package inbound - -import ( - "net" - "net/http" - "strings" - - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" -) - -// NewHTTP receive normal http request and return HTTPContext -func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext { - metadata := parseHTTPAddr(request) - metadata.Type = C.HTTP - if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { - metadata.SrcIP = ip - metadata.SrcPort = port - } - return context.NewHTTPContext(conn, request, metadata) -} - -// RemoveHopByHopHeaders remove hop-by-hop header -func RemoveHopByHopHeaders(header http.Header) { - // Strip hop-by-hop header based on RFC: - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 - // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do - - header.Del("Proxy-Connection") - header.Del("Proxy-Authenticate") - header.Del("Proxy-Authorization") - header.Del("TE") - header.Del("Trailers") - header.Del("Transfer-Encoding") - header.Del("Upgrade") - - connections := header.Get("Connection") - header.Del("Connection") - if len(connections) == 0 { - return - } - for _, h := range strings.Split(connections, ",") { - header.Del(strings.TrimSpace(h)) - } -} - -// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com) -// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com) -func RemoveExtraHTTPHostPort(req *http.Request) { - host := req.Host - if host == "" { - host = req.URL.Host - } - - if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" { - host = pHost - } - - req.Host = host - req.URL.Host = host -} diff --git a/common/batch/batch.go b/common/batch/batch.go new file mode 100644 index 000000000..ce20ed612 --- /dev/null +++ b/common/batch/batch.go @@ -0,0 +1,105 @@ +package batch + +import ( + "context" + "sync" +) + +type Option = func(b *Batch) + +type Result struct { + Value interface{} + Err error +} + +type Error struct { + Key string + Err error +} + +func WithConcurrencyNum(n int) Option { + return func(b *Batch) { + q := make(chan struct{}, n) + for i := 0; i < n; i++ { + q <- struct{}{} + } + b.queue = q + } +} + +// Batch similar to errgroup, but can control the maximum number of concurrent +type Batch struct { + result map[string]Result + queue chan struct{} + wg sync.WaitGroup + mux sync.Mutex + err *Error + once sync.Once + cancel func() +} + +func (b *Batch) Go(key string, fn func() (interface{}, error)) { + b.wg.Add(1) + go func() { + defer b.wg.Done() + if b.queue != nil { + <-b.queue + defer func() { + b.queue <- struct{}{} + }() + } + + value, err := fn() + if err != nil { + b.once.Do(func() { + b.err = &Error{key, err} + if b.cancel != nil { + b.cancel() + } + }) + } + + ret := Result{value, err} + b.mux.Lock() + defer b.mux.Unlock() + b.result[key] = ret + }() +} + +func (b *Batch) Wait() *Error { + b.wg.Wait() + if b.cancel != nil { + b.cancel() + } + return b.err +} + +func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) { + err := b.Wait() + return b.Result(), err +} + +func (b *Batch) Result() map[string]Result { + b.mux.Lock() + defer b.mux.Unlock() + copy := map[string]Result{} + for k, v := range b.result { + copy[k] = v + } + return copy +} + +func New(ctx context.Context, opts ...Option) (*Batch, context.Context) { + ctx, cancel := context.WithCancel(ctx) + + b := &Batch{ + result: map[string]Result{}, + } + + for _, o := range opts { + o(b) + } + + b.cancel = cancel + return b, ctx +} diff --git a/common/batch/batch_test.go b/common/batch/batch_test.go new file mode 100644 index 000000000..9465dbad9 --- /dev/null +++ b/common/batch/batch_test.go @@ -0,0 +1,83 @@ +package batch + +import ( + "context" + "errors" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestBatch(t *testing.T) { + b, _ := New(context.Background()) + + now := time.Now() + b.Go("foo", func() (interface{}, error) { + time.Sleep(time.Millisecond * 100) + return "foo", nil + }) + b.Go("bar", func() (interface{}, error) { + time.Sleep(time.Millisecond * 150) + return "bar", nil + }) + result, err := b.WaitAndGetResult() + + assert.Nil(t, err) + + duration := time.Since(now) + assert.Less(t, duration, time.Millisecond*200) + assert.Equal(t, 2, len(result)) + + for k, v := range result { + assert.NoError(t, v.Err) + assert.Equal(t, k, v.Value.(string)) + } +} + +func TestBatchWithConcurrencyNum(t *testing.T) { + b, _ := New( + context.Background(), + WithConcurrencyNum(3), + ) + + now := time.Now() + for i := 0; i < 7; i++ { + idx := i + b.Go(strconv.Itoa(idx), func() (interface{}, error) { + time.Sleep(time.Millisecond * 100) + return strconv.Itoa(idx), nil + }) + } + result, _ := b.WaitAndGetResult() + duration := time.Since(now) + assert.Greater(t, duration, time.Millisecond*260) + assert.Equal(t, 7, len(result)) + + for k, v := range result { + assert.NoError(t, v.Err) + assert.Equal(t, k, v.Value.(string)) + } +} + +func TestBatchContext(t *testing.T) { + b, ctx := New(context.Background()) + + b.Go("error", func() (interface{}, error) { + time.Sleep(time.Millisecond * 100) + return nil, errors.New("test error") + }) + + b.Go("ctx", func() (interface{}, error) { + <-ctx.Done() + return nil, ctx.Err() + }) + + result, err := b.WaitAndGetResult() + + assert.NotNil(t, err) + assert.Equal(t, "error", err.Key) + + assert.Equal(t, ctx.Err(), result["ctx"].Err) +} diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go index 13675bff3..8a04f7464 100644 --- a/common/cache/lrucache_test.go +++ b/common/cache/lrucache_test.go @@ -149,7 +149,6 @@ func TestSetWithExpire(t *testing.T) { assert.Equal(t, nil, res) assert.Equal(t, time.Time{}, expires) assert.Equal(t, false, exist) - } func TestStale(t *testing.T) { diff --git a/common/murmur3/murmur32.go b/common/murmur3/murmur32.go index 9861eecd8..e52b7937d 100644 --- a/common/murmur3/murmur32.go +++ b/common/murmur3/murmur32.go @@ -67,7 +67,6 @@ func (d *digest32) bmix(p []byte) (tail []byte) { } func (d *digest32) Sum32() (h1 uint32) { - h1 = d.h1 var k1 uint32 diff --git a/proxy/mixed/conn.go b/common/net/bufconn.go similarity index 91% rename from proxy/mixed/conn.go rename to common/net/bufconn.go index f009a0060..a50c7f030 100644 --- a/proxy/mixed/conn.go +++ b/common/net/bufconn.go @@ -1,4 +1,4 @@ -package mixed +package net import ( "bufio" @@ -11,6 +11,9 @@ type BufferedConn struct { } func NewBufferedConn(c net.Conn) *BufferedConn { + if bc, ok := c.(*BufferedConn); ok { + return bc + } return &BufferedConn{bufio.NewReader(c), c} } diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index cb16ad399..b70feec87 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -38,7 +38,7 @@ func TestObservable_MultiSubscribe(t *testing.T) { src := NewObservable(iter) ch1, _ := src.Subscribe() ch2, _ := src.Subscribe() - var count = atomic.NewInt32(0) + count := atomic.NewInt32(0) var wg sync.WaitGroup wg.Add(2) diff --git a/common/pool/alloc.go b/common/pool/alloc.go index bf177da88..710639af8 100644 --- a/common/pool/alloc.go +++ b/common/pool/alloc.go @@ -8,11 +8,7 @@ import ( "sync" ) -var defaultAllocator *Allocator - -func init() { - defaultAllocator = NewAllocator() -} +var defaultAllocator = NewAllocator() // Allocator for incoming frames, optimized to prevent overwriting after zeroing type Allocator struct { @@ -57,6 +53,7 @@ func (alloc *Allocator) Put(buf []byte) error { } //lint:ignore SA6002 ignore temporarily + //nolint alloc.buffers[bits].Put(buf) return nil } diff --git a/common/pool/buffer.go b/common/pool/buffer.go new file mode 100644 index 000000000..fb328b766 --- /dev/null +++ b/common/pool/buffer.go @@ -0,0 +1,17 @@ +package pool + +import ( + "bytes" + "sync" +) + +var bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} + +func GetBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func PutBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} diff --git a/common/pool/pool.go b/common/pool/pool.go index e6354f119..bee4887f5 100644 --- a/common/pool/pool.go +++ b/common/pool/pool.go @@ -5,6 +5,11 @@ const ( // but the maximum packet size of vmess/shadowsocks is about 16 KiB // so define a buffer of 20 KiB to reduce the memory of each TCP relay RelayBufferSize = 20 * 1024 + + // RelayBufferSize uses 20KiB, but due to the allocator it will actually + // request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU + // set to 9000, so the UDP Buffer size set to 16Kib + UDPBufferSize = 16 * 1024 ) func Get(size int) []byte { diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index 2b0d5988b..c85795c20 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -12,7 +12,7 @@ import ( func TestBasic(t *testing.T) { single := NewSingle(time.Millisecond * 30) foo := 0 - var shardCount = atomic.NewInt32(0) + shardCount := atomic.NewInt32(0) call := func() (interface{}, error) { foo++ time.Sleep(time.Millisecond * 5) diff --git a/common/sockopt/reuseaddr_other.go b/common/sockopt/reuseaddr_other.go index 2b1369508..c07083de0 100644 --- a/common/sockopt/reuseaddr_other.go +++ b/common/sockopt/reuseaddr_other.go @@ -1,3 +1,4 @@ +//go:build !linux // +build !linux package sockopt diff --git a/common/structure/structure.go b/common/structure/structure.go index 75579ffcb..07043abc4 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -37,6 +37,12 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error { v := reflect.ValueOf(dst).Elem() for idx := 0; idx < v.NumField(); idx++ { field := t.Field(idx) + if field.Anonymous { + if err := d.decodeStruct(field.Name, src, v.Field(idx)); err != nil { + return err + } + continue + } tag := field.Tag.Get(d.option.TagName) str := strings.SplitN(tag, ",", 2) diff --git a/common/structure/structure_test.go b/common/structure/structure_test.go index 6e3e6290d..69268fa60 100644 --- a/common/structure/structure_test.go +++ b/common/structure/structure_test.go @@ -1,12 +1,15 @@ package structure import ( - "reflect" "testing" + + "github.com/stretchr/testify/assert" ) -var decoder = NewDecoder(Option{TagName: "test"}) -var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) +var ( + decoder = NewDecoder(Option{TagName: "test"}) + weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) +) type Baz struct { Foo int `test:"foo"` @@ -37,12 +40,8 @@ func TestStructure_Basic(t *testing.T) { s := &Baz{} err := decoder.Decode(rawMap, s) - if err != nil { - t.Fatal(err.Error()) - } - if !reflect.DeepEqual(s, goal) { - t.Fatalf("bad: %#v", s) - } + assert.Nil(t, err) + assert.Equal(t, goal, s) } func TestStructure_Slice(t *testing.T) { @@ -58,12 +57,8 @@ func TestStructure_Slice(t *testing.T) { s := &BazSlice{} err := decoder.Decode(rawMap, s) - if err != nil { - t.Fatal(err.Error()) - } - if !reflect.DeepEqual(s, goal) { - t.Fatalf("bad: %#v", s) - } + assert.Nil(t, err) + assert.Equal(t, goal, s) } func TestStructure_Optional(t *testing.T) { @@ -77,12 +72,8 @@ func TestStructure_Optional(t *testing.T) { s := &BazOptional{} err := decoder.Decode(rawMap, s) - if err != nil { - t.Fatal(err.Error()) - } - if !reflect.DeepEqual(s, goal) { - t.Fatalf("bad: %#v", s) - } + assert.Nil(t, err) + assert.Equal(t, goal, s) } func TestStructure_MissingKey(t *testing.T) { @@ -92,18 +83,14 @@ func TestStructure_MissingKey(t *testing.T) { s := &Baz{} err := decoder.Decode(rawMap, s) - if err == nil { - t.Fatalf("should throw error: %#v", s) - } + assert.NotNilf(t, err, "should throw error: %#v", s) } func TestStructure_ParamError(t *testing.T) { rawMap := map[string]interface{}{} s := Baz{} err := decoder.Decode(rawMap, s) - if err == nil { - t.Fatalf("should throw error: %#v", s) - } + assert.NotNilf(t, err, "should throw error: %#v", s) } func TestStructure_SliceTypeError(t *testing.T) { @@ -114,9 +101,7 @@ func TestStructure_SliceTypeError(t *testing.T) { s := &BazSlice{} err := decoder.Decode(rawMap, s) - if err == nil { - t.Fatalf("should throw error: %#v", s) - } + assert.NotNilf(t, err, "should throw error: %#v", s) } func TestStructure_WeakType(t *testing.T) { @@ -132,10 +117,23 @@ func TestStructure_WeakType(t *testing.T) { s := &BazSlice{} err := weakTypeDecoder.Decode(rawMap, s) - if err != nil { - t.Fatal(err.Error()) - } - if !reflect.DeepEqual(s, goal) { - t.Fatalf("bad: %#v", s) - } + assert.Nil(t, err) + assert.Equal(t, goal, s) +} + +func TestStructure_Nest(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": 1, + } + + goal := BazOptional{ + Foo: 1, + } + + s := &struct { + BazOptional + }{} + err := decoder.Decode(rawMap, s) + assert.Nil(t, err) + assert.Equal(t, s.BazOptional, goal) } diff --git a/component/dhcp/conn.go b/component/dhcp/conn.go new file mode 100644 index 000000000..90a9e25b5 --- /dev/null +++ b/component/dhcp/conn.go @@ -0,0 +1,18 @@ +package dhcp + +import ( + "context" + "net" + "runtime" + + "github.com/Dreamacro/clash/component/dialer" +) + +func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) { + listenAddr := "0.0.0.0:68" + if runtime.GOOS == "linux" || runtime.GOOS == "android" { + listenAddr = "255.255.255.255:68" + } + + return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true)) +} diff --git a/component/dhcp/dhcp.go b/component/dhcp/dhcp.go new file mode 100644 index 000000000..b7e9f5069 --- /dev/null +++ b/component/dhcp/dhcp.go @@ -0,0 +1,88 @@ +package dhcp + +import ( + "context" + "errors" + "net" + + "github.com/Dreamacro/clash/component/iface" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +var ( + ErrNotResponding = errors.New("DHCP not responding") + ErrNotFound = errors.New("DNS option not found") +) + +func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, error) { + conn, err := ListenDHCPClient(context, ifaceName) + if err != nil { + return nil, err + } + defer conn.Close() + + result := make(chan []net.IP, 1) + + ifaceObj, err := iface.ResolveInterface(ifaceName) + if err != nil { + return nil, err + } + + discovery, err := dhcpv4.NewDiscovery(ifaceObj.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) + if err != nil { + return nil, err + } + + go receiveOffer(conn, discovery.TransactionID, result) + + _, err = conn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67}) + if err != nil { + return nil, err + } + + select { + case r, ok := <-result: + if !ok { + return nil, ErrNotFound + } + return r, nil + case <-context.Done(): + return nil, ErrNotResponding + } +} + +func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []net.IP) { + defer close(result) + + buf := make([]byte, dhcpv4.MaxMessageSize) + + for { + n, _, err := conn.ReadFrom(buf) + if err != nil { + return + } + + pkt, err := dhcpv4.FromBytes(buf[:n]) + if err != nil { + continue + } + + if pkt.MessageType() != dhcpv4.MessageTypeOffer { + continue + } + + if pkt.TransactionID != id { + continue + } + + dns := pkt.DNS() + if len(dns) == 0 { + return + } + + result <- dns + + return + } +} diff --git a/component/dialer/bind.go b/component/dialer/bind.go deleted file mode 100644 index 0ad520683..000000000 --- a/component/dialer/bind.go +++ /dev/null @@ -1,118 +0,0 @@ -package dialer - -import ( - "errors" - "net" - "time" - - "github.com/Dreamacro/clash/common/singledo" -) - -// In some OS, such as Windows, it takes a little longer to get interface information -var ifaceSingle = singledo.NewSingle(time.Second * 20) - -var ( - errPlatformNotSupport = errors.New("unsupport platform") -) - -func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) { - ipv4 := ip.To4() != nil - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok { - continue - } - - addrV4 := addr.IP.To4() != nil - - if addrV4 && ipv4 { - return &net.TCPAddr{IP: addr.IP, Port: 0}, nil - } else if !addrV4 && !ipv4 { - return &net.TCPAddr{IP: addr.IP, Port: 0}, nil - } - } - - return nil, ErrAddrNotFound -} - -func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { - ipv4 := ip.To4() != nil - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok { - continue - } - - addrV4 := addr.IP.To4() != nil - - if addrV4 && ipv4 { - return &net.UDPAddr{IP: addr.IP, Port: 0}, nil - } else if !addrV4 && !ipv4 { - return &net.UDPAddr{IP: addr.IP, Port: 0}, nil - } - } - - return nil, ErrAddrNotFound -} - -func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error { - if !ip.IsGlobalUnicast() { - return nil - } - - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(name) - }) - if err != nil { - return err - } - - addrs, err := iface.(*net.Interface).Addrs() - if err != nil { - return err - } - - switch network { - case "tcp", "tcp4", "tcp6": - if addr, err := lookupTCPAddr(ip, addrs); err == nil { - dialer.LocalAddr = addr - } else { - return err - } - case "udp", "udp4", "udp6": - if addr, err := lookupUDPAddr(ip, addrs); err == nil { - dialer.LocalAddr = addr - } else { - return err - } - } - - return nil -} - -func fallbackBindToListenConfig(name string) (string, error) { - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(name) - }) - if err != nil { - return "", err - } - - addrs, err := iface.(*net.Interface).Addrs() - if err != nil { - return "", err - } - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok || addr.IP.To4() == nil { - continue - } - - return net.JoinHostPort(addr.IP.String(), "0"), nil - } - - return "", ErrAddrNotFound -} diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index 0ce505412..b3ae9d815 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -3,51 +3,57 @@ package dialer import ( "net" "syscall" + + "golang.org/x/sys/unix" + + "github.com/Dreamacro/clash/component/iface" ) type controlFn = func(network, address string, c syscall.RawConn) error -func bindControl(ifaceIdx int) controlFn { - return func(network, address string, c syscall.RawConn) error { +func bindControl(ifaceIdx int, chain controlFn) controlFn { + return func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + ipStr, _, err := net.SplitHostPort(address) if err == nil { ip := net.ParseIP(ipStr) if ip != nil && !ip.IsGlobalUnicast() { - return nil + return } } return c.Control(func(fd uintptr) { switch network { case "tcp4", "udp4": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx) + unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx) case "tcp6", "udp6": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx) + unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx) } }) } } -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(ifaceName) - }) +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error { + ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { return err } - dialer.Control = bindControl(iface.(*net.Interface).Index) + dialer.Control = bindControl(ifaceObj.Index, dialer.Control) return nil } -func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { - return net.InterfaceByName(ifaceName) - }) +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { + ifaceObj, err := iface.ResolveInterface(ifaceName) if err != nil { - return err + return "", err } - lc.Control = bindControl(iface.(*net.Interface).Index) - return nil + lc.Control = bindControl(ifaceObj.Index, lc.Control) + return address, nil } diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go index 7e9d308dd..ae772a71e 100644 --- a/component/dialer/bind_linux.go +++ b/component/dialer/bind_linux.go @@ -3,34 +3,42 @@ package dialer import ( "net" "syscall" + + "golang.org/x/sys/unix" ) type controlFn = func(network, address string, c syscall.RawConn) error -func bindControl(ifaceName string) controlFn { - return func(network, address string, c syscall.RawConn) error { +func bindControl(ifaceName string, chain controlFn) controlFn { + return func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + ipStr, _, err := net.SplitHostPort(address) if err == nil { ip := net.ParseIP(ipStr) if ip != nil && !ip.IsGlobalUnicast() { - return nil + return } } return c.Control(func(fd uintptr) { - syscall.BindToDevice(int(fd), ifaceName) + unix.BindToDevice(int(fd), ifaceName) }) } } -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - dialer.Control = bindControl(ifaceName) +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error { + dialer.Control = bindControl(ifaceName, dialer.Control) return nil } -func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - lc.Control = bindControl(ifaceName) +func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { + lc.Control = bindControl(ifaceName, lc.Control) - return nil + return address, nil } diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index be30bae84..a74995b58 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -1,13 +1,93 @@ +//go:build !linux && !darwin // +build !linux,!darwin package dialer -import "net" +import ( + "net" + "strconv" + "strings" -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - return errPlatformNotSupport + "github.com/Dreamacro/clash/component/iface" +) + +func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) { + ifaceObj, err := iface.ResolveInterface(ifaceName) + if err != nil { + return nil, err + } + + var addr *net.IPNet + switch network { + case "udp4", "tcp4": + addr, err = ifaceObj.PickIPv4Addr(destination) + case "tcp6", "udp6": + addr, err = ifaceObj.PickIPv6Addr(destination) + default: + if destination != nil { + if destination.To4() != nil { + addr, err = ifaceObj.PickIPv4Addr(destination) + } else { + addr, err = ifaceObj.PickIPv6Addr(destination) + } + } else { + addr, err = ifaceObj.PickIPv4Addr(destination) + } + } + if err != nil { + return nil, err + } + + if strings.HasPrefix(network, "tcp") { + return &net.TCPAddr{ + IP: addr.IP, + Port: port, + }, nil + } else if strings.HasPrefix(network, "udp") { + return &net.UDPAddr{ + IP: addr.IP, + Port: port, + }, nil + } + + return nil, iface.ErrAddrNotFound } -func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - return errPlatformNotSupport +func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error { + if !destination.IsGlobalUnicast() { + return nil + } + + local := int64(0) + if dialer.LocalAddr != nil { + _, port, err := net.SplitHostPort(dialer.LocalAddr.String()) + if err == nil { + local, _ = strconv.ParseInt(port, 10, 16) + } + } + + addr, err := lookupLocalAddr(ifaceName, network, destination, int(local)) + if err != nil { + return err + } + + dialer.LocalAddr = addr + + return nil +} + +func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { + _, port, err := net.SplitHostPort(address) + if err != nil { + port = "0" + } + + local, _ := strconv.ParseInt(port, 10, 16) + + addr, err := lookupLocalAddr(ifaceName, network, nil, int(local)) + if err != nil { + return "", err + } + + return addr.String(), nil } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index be26681a7..c84fcaa46 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -8,22 +8,7 @@ import ( "github.com/Dreamacro/clash/component/resolver" ) -func Dialer() (*net.Dialer, error) { - dialer := &net.Dialer{} - if DialerHook != nil { - if err := DialerHook(dialer); err != nil { - return nil, err - } - } - - return dialer, nil -} - -func Dial(network, address string) (net.Conn, error) { - return DialContext(context.Background(), network, address) -} - -func DialContext(ctx context.Context, network, address string) (net.Conn, error) { +func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { switch network { case "tcp4", "tcp6", "udp4", "udp6": host, port, err := net.SplitHostPort(address) @@ -31,11 +16,6 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) return nil, err } - dialer, err := Dialer() - if err != nil { - return nil, err - } - var ip net.IP switch network { case "tcp4", "udp4": @@ -43,38 +23,76 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) default: ip, err = resolver.ResolveIPv6(host) } - if err != nil { return nil, err } - if DialHook != nil { - if err := DialHook(dialer, network, ip); err != nil { - return nil, err - } - } - return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + return dialContext(ctx, network, ip, port, options) case "tcp", "udp": - return dualStackDialContext(ctx, network, address) + return dualStackDialContext(ctx, network, address, options) default: return nil, errors.New("network invalid") } } -func ListenPacket(network, address string) (net.PacketConn, error) { - cfg := &net.ListenConfig{} - if ListenPacketHook != nil { - var err error - address, err = ListenPacketHook(cfg, address) +func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { + cfg := &option{ + interfaceName: DefaultInterface.Load(), + } + + for _, o := range DefaultOptions { + o(cfg) + } + + for _, o := range options { + o(cfg) + } + + lc := &net.ListenConfig{} + if cfg.interfaceName != "" { + addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) if err != nil { return nil, err } + address = addr + } + if cfg.addrReuse { + addrReuseToListenConfig(lc) + } + if cfg.routingMark != 0 { + bindMarkToListenConfig(cfg.routingMark, lc, network, address) } - return cfg.ListenPacket(context.Background(), network, address) + return lc.ListenPacket(ctx, network, address) } -func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) { +func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) { + opt := &option{ + interfaceName: DefaultInterface.Load(), + } + + for _, o := range DefaultOptions { + o(opt) + } + + for _, o := range options { + o(opt) + } + + dialer := &net.Dialer{} + if opt.interfaceName != "" { + if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { + return nil, err + } + } + if opt.routingMark != 0 { + bindMarkToDialer(opt.routingMark, dialer, network, destination) + } + + return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) +} + +func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err @@ -105,12 +123,6 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con } }() - dialer, err := Dialer() - if err != nil { - result.error = err - return - } - var ip net.IP if ipv6 { ip, result.error = resolver.ResolveIPv6(host) @@ -122,12 +134,7 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con } result.resolved = true - if DialHook != nil { - if result.error = DialHook(dialer, network, ip); result.error != nil { - return - } - } - result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + result.Conn, result.error = dialContext(ctx, network, ip, port, options) } go startRacer(ctx, network+"4", host, false) diff --git a/component/dialer/hook.go b/component/dialer/hook.go deleted file mode 100644 index 356e4b25f..000000000 --- a/component/dialer/hook.go +++ /dev/null @@ -1,43 +0,0 @@ -package dialer - -import ( - "errors" - "net" -) - -type DialerHookFunc = func(dialer *net.Dialer) error -type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error -type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error) - -var ( - DialerHook DialerHookFunc - DialHook DialHookFunc - ListenPacketHook ListenPacketHookFunc -) - -var ( - ErrAddrNotFound = errors.New("addr not found") - ErrNetworkNotSupport = errors.New("network not support") -) - -func ListenPacketWithInterface(name string) ListenPacketHookFunc { - return func(lc *net.ListenConfig, address string) (string, error) { - err := bindIfaceToListenConfig(lc, name) - if err == errPlatformNotSupport { - address, err = fallbackBindToListenConfig(name) - } - - return address, err - } -} - -func DialerWithInterface(name string) DialHookFunc { - return func(dialer *net.Dialer, network string, ip net.IP) error { - err := bindIfaceToDialer(dialer, name) - if err == errPlatformNotSupport { - err = fallbackBindToDialer(dialer, network, ip, name) - } - - return err - } -} diff --git a/component/dialer/mark_linux.go b/component/dialer/mark_linux.go new file mode 100644 index 000000000..79a2185e4 --- /dev/null +++ b/component/dialer/mark_linux.go @@ -0,0 +1,44 @@ +//go:build linux +// +build linux + +package dialer + +import ( + "net" + "syscall" +) + +func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) { + dialer.Control = bindMarkToControl(mark, dialer.Control) +} + +func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) { + lc.Control = bindMarkToControl(mark, lc.Control) +} + +func bindMarkToControl(mark int, chain controlFn) controlFn { + return func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + + ipStr, _, err := net.SplitHostPort(address) + if err == nil { + ip := net.ParseIP(ipStr) + if ip != nil && !ip.IsGlobalUnicast() { + return + } + } + + return c.Control(func(fd uintptr) { + switch network { + case "tcp4", "udp4": + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) + case "tcp6", "udp6": + syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) + } + }) + } +} diff --git a/component/dialer/mark_nonlinux.go b/component/dialer/mark_nonlinux.go new file mode 100644 index 000000000..5d9befb1a --- /dev/null +++ b/component/dialer/mark_nonlinux.go @@ -0,0 +1,27 @@ +//go:build !linux +// +build !linux + +package dialer + +import ( + "net" + "sync" + + "github.com/Dreamacro/clash/log" +) + +var printMarkWarnOnce sync.Once + +func printMarkWarn() { + printMarkWarnOnce.Do(func() { + log.Warnln("Routing mark on socket is not supported on current platform") + }) +} + +func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) { + printMarkWarn() +} + +func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) { + printMarkWarn() +} diff --git a/component/dialer/options.go b/component/dialer/options.go new file mode 100644 index 000000000..b3cca8105 --- /dev/null +++ b/component/dialer/options.go @@ -0,0 +1,34 @@ +package dialer + +import "go.uber.org/atomic" + +var ( + DefaultOptions []Option + DefaultInterface = atomic.NewString("") +) + +type option struct { + interfaceName string + addrReuse bool + routingMark int +} + +type Option func(opt *option) + +func WithInterface(name string) Option { + return func(opt *option) { + opt.interfaceName = name + } +} + +func WithAddrReuse(reuse bool) Option { + return func(opt *option) { + opt.addrReuse = reuse + } +} + +func WithRoutingMark(mark int) Option { + return func(opt *option) { + opt.routingMark = mark + } +} diff --git a/component/dialer/reuse_others.go b/component/dialer/reuse_others.go new file mode 100644 index 000000000..b76213a7f --- /dev/null +++ b/component/dialer/reuse_others.go @@ -0,0 +1,10 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows + +package dialer + +import ( + "net" +) + +func addrReuseToListenConfig(*net.ListenConfig) {} diff --git a/component/dialer/reuse_unix.go b/component/dialer/reuse_unix.go new file mode 100644 index 000000000..d5f43d8fe --- /dev/null +++ b/component/dialer/reuse_unix.go @@ -0,0 +1,28 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package dialer + +import ( + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +func addrReuseToListenConfig(lc *net.ListenConfig) { + chain := lc.Control + + lc.Control = func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + + return c.Control(func(fd uintptr) { + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) + } +} diff --git a/component/dialer/reuse_windows.go b/component/dialer/reuse_windows.go new file mode 100644 index 000000000..77fcf7ab2 --- /dev/null +++ b/component/dialer/reuse_windows.go @@ -0,0 +1,24 @@ +package dialer + +import ( + "net" + "syscall" + + "golang.org/x/sys/windows" +) + +func addrReuseToListenConfig(lc *net.ListenConfig) { + chain := lc.Control + + lc.Control = func(network, address string, c syscall.RawConn) (err error) { + defer func() { + if err == nil && chain != nil { + err = chain(network, address, c) + } + }() + + return c.Control(func(fd uintptr) { + windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) + }) + } +} diff --git a/component/fakeip/cachefile.go b/component/fakeip/cachefile.go new file mode 100644 index 000000000..7ee889812 --- /dev/null +++ b/component/fakeip/cachefile.go @@ -0,0 +1,49 @@ +package fakeip + +import ( + "net" + + "github.com/Dreamacro/clash/component/profile/cachefile" +) + +type cachefileStore struct { + cache *cachefile.CacheFile +} + +// GetByHost implements store.GetByHost +func (c *cachefileStore) GetByHost(host string) (net.IP, bool) { + elm := c.cache.GetFakeip([]byte(host)) + if elm == nil { + return nil, false + } + return net.IP(elm), true +} + +// PutByHost implements store.PutByHost +func (c *cachefileStore) PutByHost(host string, ip net.IP) { + c.cache.PutFakeip([]byte(host), ip) +} + +// GetByIP implements store.GetByIP +func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) { + elm := c.cache.GetFakeip(ip.To4()) + if elm == nil { + return "", false + } + return string(elm), true +} + +// PutByIP implements store.PutByIP +func (c *cachefileStore) PutByIP(ip net.IP, host string) { + c.cache.PutFakeip(ip.To4(), []byte(host)) +} + +// Exist implements store.Exist +func (c *cachefileStore) Exist(ip net.IP) bool { + _, exist := c.GetByIP(ip) + return exist +} + +// CloneTo implements store.CloneTo +// already persistence +func (c *cachefileStore) CloneTo(store store) {} diff --git a/component/fakeip/memory.go b/component/fakeip/memory.go new file mode 100644 index 000000000..75d4a3b24 --- /dev/null +++ b/component/fakeip/memory.go @@ -0,0 +1,60 @@ +package fakeip + +import ( + "net" + + "github.com/Dreamacro/clash/common/cache" +) + +type memoryStore struct { + cache *cache.LruCache +} + +// GetByHost implements store.GetByHost +func (m *memoryStore) GetByHost(host string) (net.IP, bool) { + if elm, exist := m.cache.Get(host); exist { + ip := elm.(net.IP) + + // ensure ip --> host on head of linked list + m.cache.Get(ipToUint(ip.To4())) + return ip, true + } + + return nil, false +} + +// PutByHost implements store.PutByHost +func (m *memoryStore) PutByHost(host string, ip net.IP) { + m.cache.Set(host, ip) +} + +// GetByIP implements store.GetByIP +func (m *memoryStore) GetByIP(ip net.IP) (string, bool) { + if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist { + host := elm.(string) + + // ensure host --> ip on head of linked list + m.cache.Get(host) + return host, true + } + + return "", false +} + +// PutByIP implements store.PutByIP +func (m *memoryStore) PutByIP(ip net.IP, host string) { + m.cache.Set(ipToUint(ip.To4()), host) +} + +// Exist implements store.Exist +func (m *memoryStore) Exist(ip net.IP) bool { + return m.cache.Exist(ipToUint(ip.To4())) +} + +// CloneTo implements store.CloneTo +// only for memoryStore to memoryStore +func (m *memoryStore) CloneTo(store store) { + if ms, ok := store.(*memoryStore); ok { + m.cache.CloneTo(ms.cache) + } +} diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 97d812ac3..180d5eb18 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -6,9 +6,19 @@ import ( "sync" "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/trie" ) +type store interface { + GetByHost(host string) (net.IP, bool) + PutByHost(host string, ip net.IP) + GetByIP(ip net.IP) (string, bool) + PutByIP(ip net.IP, host string) + Exist(ip net.IP) bool + CloneTo(store) +} + // Pool is a implementation about fake ip generator without storage type Pool struct { max uint32 @@ -18,25 +28,19 @@ type Pool struct { mux sync.Mutex host *trie.DomainTrie ipnet *net.IPNet - cache *cache.LruCache + store store } // Lookup return a fake ip with host func (p *Pool) Lookup(host string) net.IP { p.mux.Lock() defer p.mux.Unlock() - if elm, exist := p.cache.Get(host); exist { - ip := elm.(net.IP) - - // ensure ip --> host on head of linked list - n := ipToUint(ip.To4()) - offset := n - p.min + 1 - p.cache.Get(offset) + if ip, exist := p.store.GetByHost(host); exist { return ip } ip := p.get(host) - p.cache.Set(host, ip) + p.store.PutByHost(host, ip) return ip } @@ -49,22 +53,11 @@ func (p *Pool) LookBack(ip net.IP) (string, bool) { return "", false } - n := ipToUint(ip.To4()) - offset := n - p.min + 1 - - if elm, exist := p.cache.Get(offset); exist { - host := elm.(string) - - // ensure host --> ip on head of linked list - p.cache.Get(host) - return host, true - } - - return "", false + return p.store.GetByIP(ip) } -// LookupHost return if domain in host -func (p *Pool) LookupHost(domain string) bool { +// ShouldSkipped return if domain should be skipped +func (p *Pool) ShouldSkipped(domain string) bool { if p.host == nil { return false } @@ -80,9 +73,7 @@ func (p *Pool) Exist(ip net.IP) bool { return false } - n := ipToUint(ip.To4()) - offset := n - p.min + 1 - return p.cache.Exist(offset) + return p.store.Exist(ip) } // Gateway return gateway ip @@ -95,9 +86,9 @@ func (p *Pool) IPNet() *net.IPNet { return p.ipnet } -// PatchFrom clone cache from old pool -func (p *Pool) PatchFrom(o *Pool) { - o.cache.CloneTo(p.cache) +// CloneFrom clone cache from old pool +func (p *Pool) CloneFrom(o *Pool) { + o.store.CloneTo(p.store) } func (p *Pool) get(host string) net.IP { @@ -109,12 +100,13 @@ func (p *Pool) get(host string) net.IP { break } - if !p.cache.Exist(p.offset) { + ip := uintToIP(p.min + p.offset - 1) + if !p.store.Exist(ip) { break } } ip := uintToIP(p.min + p.offset - 1) - p.cache.Set(p.offset, host) + p.store.PutByIP(ip, host) return ip } @@ -130,11 +122,24 @@ func uintToIP(v uint32) net.IP { return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)} } -// New return Pool instance -func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { - min := ipToUint(ipnet.IP) + 2 +type Options struct { + IPNet *net.IPNet + Host *trie.DomainTrie - ones, bits := ipnet.Mask.Size() + // Size sets the maximum number of entries in memory + // and does not work if Persistence is true + Size int + + // Persistence will save the data to disk. + // Size will not work and record will be fully stored. + Persistence bool +} + +// New return Pool instance +func New(options Options) (*Pool, error) { + min := ipToUint(options.IPNet.IP) + 2 + + ones, bits := options.IPNet.Mask.Size() total := 1< 0 { - return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ",")) - } - return nil -} - -func (wsc *websocketConn) getReader() (io.Reader, error) { - if wsc.reader != nil { - return wsc.reader, nil - } - - _, reader, err := wsc.conn.NextReader() - if err != nil { - return nil, err - } - wsc.reader = reader - return reader, nil -} - -func (wsc *websocketConn) LocalAddr() net.Addr { - return wsc.conn.LocalAddr() -} - -func (wsc *websocketConn) RemoteAddr() net.Addr { - return wsc.remoteAddr -} - -func (wsc *websocketConn) SetDeadline(t time.Time) error { - if err := wsc.SetReadDeadline(t); err != nil { - return err - } - return wsc.SetWriteDeadline(t) -} - -func (wsc *websocketConn) SetReadDeadline(t time.Time) error { - return wsc.conn.SetReadDeadline(t) -} - -func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { - return wsc.conn.SetWriteDeadline(t) -} - -func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { - dialer := &websocket.Dialer{ - NetDial: func(network, addr string) (net.Conn, error) { - return conn, nil - }, - ReadBufferSize: 4 * 1024, - WriteBufferSize: 4 * 1024, - HandshakeTimeout: time.Second * 8, - } - - scheme := "ws" - if c.TLS { - scheme = "wss" - dialer.TLSClientConfig = &tls.Config{ - ServerName: c.Host, - InsecureSkipVerify: c.SkipCertVerify, - ClientSessionCache: c.SessionCache, - } - - if c.ServerName != "" { - dialer.TLSClientConfig.ServerName = c.ServerName - } else if host := c.Headers.Get("Host"); host != "" { - dialer.TLSClientConfig.ServerName = host - } - } - - uri := url.URL{ - Scheme: scheme, - Host: net.JoinHostPort(c.Host, c.Port), - Path: c.Path, - } - - headers := http.Header{} - if c.Headers != nil { - for k := range c.Headers { - headers.Add(k, c.Headers.Get(k)) - } - } - - wsConn, resp, err := dialer.Dial(uri.String(), headers) - if err != nil { - reason := err.Error() - if resp != nil { - reason = resp.Status - } - return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason) - } - - return &websocketConn{ - conn: wsConn, - remoteAddr: conn.RemoteAddr(), - }, nil -} diff --git a/config/config.go b/config/config.go index 51faac918..a9abe71f8 100644 --- a/config/config.go +++ b/config/config.go @@ -8,19 +8,21 @@ import ( "os" "strings" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/outboundgroup" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter" + "github.com/Dreamacro/clash/adapter/outbound" + "github.com/Dreamacro/clash/adapter/outboundgroup" + "github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" + providerTypes "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" - R "github.com/Dreamacro/clash/rules" + R "github.com/Dreamacro/clash/rule" T "github.com/Dreamacro/clash/tunnel" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" ) // General config @@ -60,22 +62,25 @@ type DNS struct { Fallback []dns.NameServer `yaml:"fallback"` FallbackFilter FallbackFilter `yaml:"fallback-filter"` Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + EnhancedMode C.DNSMode `yaml:"enhanced-mode"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` FakeIPRange *fakeip.Pool Hosts *trie.DomainTrie + NameServerPolicy map[string]dns.NameServer } // FallbackFilter config type FallbackFilter struct { - GeoIP bool `yaml:"geoip"` - IPCIDR []*net.IPNet `yaml:"ipcidr"` - Domain []string `yaml:"domain"` + GeoIP bool `yaml:"geoip"` + GeoIPCode string `yaml:"geoip-code"` + IPCIDR []*net.IPNet `yaml:"ipcidr"` + Domain []string `yaml:"domain"` } // Profile config type Profile struct { StoreSelected bool `yaml:"store-selected"` + StoreFakeIP bool `yaml:"store-fake-ip"` } // Experimental config @@ -91,7 +96,7 @@ type Config struct { Rules []C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy - Providers map[string]provider.ProxyProvider + Providers map[string]providerTypes.ProxyProvider } type RawDNS struct { @@ -102,16 +107,18 @@ type RawDNS struct { Fallback []string `yaml:"fallback"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + EnhancedMode C.DNSMode `yaml:"enhanced-mode"` FakeIPRange string `yaml:"fake-ip-range"` FakeIPFilter []string `yaml:"fake-ip-filter"` DefaultNameserver []string `yaml:"default-nameserver"` + NameServerPolicy map[string]string `yaml:"nameserver-policy"` } type RawFallbackFilter struct { - GeoIP bool `yaml:"geoip"` - IPCIDR []string `yaml:"ipcidr"` - Domain []string `yaml:"domain"` + GeoIP bool `yaml:"geoip"` + GeoIPCode string `yaml:"geoip-code"` + IPCIDR []string `yaml:"ipcidr"` + Domain []string `yaml:"domain"` } type RawConfig struct { @@ -168,8 +175,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { UseHosts: true, FakeIPRange: "198.18.0.1/16", FallbackFilter: RawFallbackFilter{ - GeoIP: true, - IPCIDR: []string{}, + GeoIP: true, + GeoIPCode: "CN", + IPCIDR: []string{}, }, DefaultNameserver: []string{ "114.114.114.114", @@ -181,7 +189,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { }, } - if err := yaml.Unmarshal(buf, &rawCfg); err != nil { + if err := yaml.Unmarshal(buf, rawCfg); err != nil { return nil, err } @@ -219,7 +227,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.Hosts = hosts - dnsCfg, err := parseDNS(rawCfg.DNS, hosts) + dnsCfg, err := parseDNS(rawCfg, hosts) if err != nil { return nil, err } @@ -264,21 +272,21 @@ func parseGeneral(cfg *RawConfig) (*General, error) { }, nil } -func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { +func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]providerTypes.ProxyProvider, err error) { proxies = make(map[string]C.Proxy) - providersMap = make(map[string]provider.ProxyProvider) + providersMap = make(map[string]providerTypes.ProxyProvider) proxyList := []string{} proxiesConfig := cfg.Proxy groupsConfig := cfg.ProxyGroup providersConfig := cfg.ProxyProvider - proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) - proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) + proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) + proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) proxyList = append(proxyList, "DIRECT", "REJECT") // parse proxy for idx, mapping := range proxiesConfig { - proxy, err := outbound.ParseProxy(mapping) + proxy, err := adapter.ParseProxy(mapping) if err != nil { return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) } @@ -337,12 +345,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName) } - proxies[groupName] = outbound.NewProxy(group) + proxies[groupName] = adapter.NewProxy(group) } // initial compatible provider for _, pd := range providersMap { - if pd.VehicleType() != provider.Compatible { + if pd.VehicleType() != providerTypes.Compatible { continue } @@ -364,9 +372,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ &outboundgroup.GroupCommonOption{ Name: "GLOBAL", }, - []provider.ProxyProvider{pd}, + []providerTypes.ProxyProvider{pd}, ) - proxies["GLOBAL"] = outbound.NewProxy(global) + proxies["GLOBAL"] = adapter.NewProxy(global) return proxies, providersMap, nil } @@ -481,6 +489,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} addr = clearURL.String() dnsNetType = "https" // DNS over HTTPS + case "dhcp": + addr = u.Host + dnsNetType = "dhcp" // UDP from DHCP default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) } @@ -500,6 +511,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { return nameservers, nil } +func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) { + policy := map[string]dns.NameServer{} + + for domain, server := range nsPolicy { + nameservers, err := parseNameServer([]string{server}) + if err != nil { + return nil, err + } + if _, valid := trie.ValidAndSplitDomain(domain); !valid { + return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain) + } + policy[domain] = nameservers[0] + } + + return policy, nil +} + func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { ipNets := []*net.IPNet{} @@ -514,7 +542,8 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { return ipNets, nil } -func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { +func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) { + cfg := rawCfg.DNS if cfg.Enable && len(cfg.NameServer) == 0 { return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") } @@ -537,6 +566,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { return nil, err } + if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil { + return nil, err + } + if len(cfg.DefaultNameserver) == 0 { return nil, errors.New("default nameserver should have at least one nameserver") } @@ -551,7 +584,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { } } - if cfg.EnhancedMode == dns.FAKEIP { + if cfg.EnhancedMode == C.DNSFakeIP { _, ipnet, err := net.ParseCIDR(cfg.FakeIPRange) if err != nil { return nil, err @@ -566,7 +599,12 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { } } - pool, err := fakeip.New(ipnet, 1000, host) + pool, err := fakeip.New(fakeip.Options{ + IPNet: ipnet, + Size: 1000, + Host: host, + Persistence: rawCfg.Profile.StoreFakeIP, + }) if err != nil { return nil, err } @@ -575,6 +613,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { } dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP + dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil { dnsCfg.FallbackFilter.IPCIDR = fallbackip } diff --git a/config/initial.go b/config/initial.go index df8452b93..9d1a2db10 100644 --- a/config/initial.go +++ b/config/initial.go @@ -18,7 +18,7 @@ func downloadMMDB(path string) (err error) { } defer resp.Body.Close() - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return err } @@ -54,7 +54,7 @@ func initMMDB() error { func Init(dir string) error { // initial homedir if _, err := os.Stat(dir); os.IsNotExist(err) { - if err := os.MkdirAll(dir, 0777); err != nil { + if err := os.MkdirAll(dir, 0o777); err != nil { return fmt.Errorf("can't create config directory %s: %s", dir, err.Error()) } } @@ -62,7 +62,7 @@ func Init(dir string) error { // initial config.yaml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { log.Infoln("Can't find config, create a initial config file") - f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) } diff --git a/config/utils.go b/config/utils.go index b3d198f7f..387591d0b 100644 --- a/config/utils.go +++ b/config/utils.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/Dreamacro/clash/adapters/outboundgroup" + "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/common/structure" ) diff --git a/constant/adapters.go b/constant/adapters.go index 333243d8a..59fde9601 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -5,6 +5,8 @@ import ( "fmt" "net" "time" + + "github.com/Dreamacro/clash/component/dialer" ) // Adapter Type @@ -27,6 +29,11 @@ const ( LoadBalance ) +const ( + DefaultTCPTimeout = 5 * time.Second + DefaultUDPTimeout = DefaultTCPTimeout +) + type Connection interface { Chains() Chain AppendToChains(adapter ProxyAdapter) @@ -69,11 +76,14 @@ type PacketConn interface { type ProxyAdapter interface { Name() string Type() AdapterType + Addr() string + SupportUDP() bool + MarshalJSON() ([]byte, error) // StreamConn wraps a protocol around net.Conn with Metadata. // // Examples: - // conn, _ := net.Dial("tcp", "host:port") + // conn, _ := net.DialContext(context.Background(), "tcp", "host:port") // conn, _ = adapter.StreamConn(conn, metadata) // // It returns a C.Conn with protocol which start with @@ -82,12 +92,10 @@ type ProxyAdapter interface { // DialContext return a C.Conn with protocol which // contains multiplexing-related reuse logic (if any) - DialContext(ctx context.Context, metadata *Metadata) (Conn, error) + DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error) + + ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error) - DialUDP(metadata *Metadata) (PacketConn, error) - SupportUDP() bool - MarshalJSON() ([]byte, error) - Addr() string // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. Unwrap(metadata *Metadata) Proxy } @@ -101,9 +109,14 @@ type Proxy interface { ProxyAdapter Alive() bool DelayHistory() []DelayHistory - Dial(metadata *Metadata) (Conn, error) LastDelay() uint16 URLTest(ctx context.Context, url string) (uint16, error) + + // Deprecated: use DialContext instead. + Dial(metadata *Metadata) (Conn, error) + + // Deprecated: use DialPacketConn instead. + DialUDP(metadata *Metadata) (PacketConn, error) } // AdapterType is enum of adapter type diff --git a/constant/dns.go b/constant/dns.go new file mode 100644 index 000000000..d1cd7a61c --- /dev/null +++ b/constant/dns.go @@ -0,0 +1,70 @@ +package constant + +import ( + "encoding/json" + "errors" +) + +// DNSModeMapping is a mapping for EnhancedMode enum +var DNSModeMapping = map[string]DNSMode{ + DNSNormal.String(): DNSNormal, + DNSFakeIP.String(): DNSFakeIP, + DNSMapping.String(): DNSMapping, +} + +const ( + DNSNormal DNSMode = iota + DNSFakeIP + DNSMapping +) + +type DNSMode int + +// UnmarshalYAML unserialize EnhancedMode with yaml +func (e *DNSMode) UnmarshalYAML(unmarshal func(interface{}) error) error { + var tp string + if err := unmarshal(&tp); err != nil { + return err + } + mode, exist := DNSModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *e = mode + return nil +} + +// MarshalYAML serialize EnhancedMode with yaml +func (e DNSMode) MarshalYAML() (interface{}, error) { + return e.String(), nil +} + +// UnmarshalJSON unserialize EnhancedMode with json +func (e *DNSMode) UnmarshalJSON(data []byte) error { + var tp string + json.Unmarshal(data, &tp) + mode, exist := DNSModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *e = mode + return nil +} + +// MarshalJSON serialize EnhancedMode with json +func (e DNSMode) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e DNSMode) String() string { + switch e { + case DNSNormal: + return "normal" + case DNSFakeIP: + return "fake-ip" + case DNSMapping: + return "redir-host" + default: + return "unknown" + } +} diff --git a/constant/listener.go b/constant/listener.go new file mode 100644 index 000000000..07782a9e0 --- /dev/null +++ b/constant/listener.go @@ -0,0 +1,7 @@ +package constant + +type Listener interface { + RawAddress() string + Address() string + Close() error +} diff --git a/constant/metadata.go b/constant/metadata.go index 93ef406d2..9cc49973f 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -17,7 +17,8 @@ const ( HTTP Type = iota HTTPCONNECT - SOCKS + SOCKS4 + SOCKS5 REDIR TPROXY ) @@ -43,7 +44,9 @@ func (t Type) String() string { return "HTTP" case HTTPCONNECT: return "HTTP Connect" - case SOCKS: + case SOCKS4: + return "Socks4" + case SOCKS5: return "Socks5" case REDIR: return "Redir" @@ -68,6 +71,7 @@ type Metadata struct { DstPort string `json:"destinationPort"` AddrType int `json:"-"` Host string `json:"host"` + DNSMode DNSMode `json:"dnsMode"` } func (m *Metadata) RemoteAddress() string { @@ -82,14 +86,31 @@ func (m *Metadata) Resolved() bool { return m.DstIP != nil } +// Pure is used to solve unexpected behavior +// when dialing proxy connection in DNSMapping mode. +func (m *Metadata) Pure() *Metadata { + if m.DNSMode == DNSMapping && m.DstIP != nil { + copy := *m + copy.Host = "" + if copy.DstIP.To4() != nil { + copy.AddrType = AtypIPv4 + } else { + copy.AddrType = AtypIPv6 + } + return © + } + + return m +} + func (m *Metadata) UDPAddr() *net.UDPAddr { if m.NetWork != UDP || m.DstIP == nil { return nil } - port, _ := strconv.Atoi(m.DstPort) + port, _ := strconv.ParseInt(m.DstPort, 10, 16) return &net.UDPAddr{ IP: m.DstIP, - Port: port, + Port: int(port), } } diff --git a/constant/path.go b/constant/path.go index 021721ec4..781cc5f7c 100644 --- a/constant/path.go +++ b/constant/path.go @@ -9,21 +9,19 @@ import ( const Name = "clash" // Path is used to get the configuration path -var Path *path - -type path struct { - homeDir string - configFile string -} - -func init() { +var Path = func() *path { homeDir, err := os.UserHomeDir() if err != nil { homeDir, _ = os.Getwd() } homeDir = P.Join(homeDir, ".config", Name) - Path = &path{homeDir: homeDir, configFile: "config.yaml"} + return &path{homeDir: homeDir, configFile: "config.yaml"} +}() + +type path struct { + homeDir string + configFile string } // SetHomeDir is used to set the configuration path @@ -57,6 +55,10 @@ func (p *path) MMDB() string { return P.Join(p.homeDir, "Country.mmdb") } -func (p *path) Cache() string { +func (p *path) OldCache() string { return P.Join(p.homeDir, ".cache") } + +func (p *path) Cache() string { + return P.Join(p.homeDir, "cache.db") +} diff --git a/constant/provider/interface.go b/constant/provider/interface.go new file mode 100644 index 000000000..53bda7eac --- /dev/null +++ b/constant/provider/interface.go @@ -0,0 +1,105 @@ +package provider + +import ( + "github.com/Dreamacro/clash/constant" +) + +// Vehicle Type +const ( + File VehicleType = iota + HTTP + Compatible +) + +// VehicleType defined +type VehicleType int + +func (v VehicleType) String() string { + switch v { + case File: + return "File" + case HTTP: + return "HTTP" + case Compatible: + return "Compatible" + default: + return "Unknown" + } +} + +type Vehicle interface { + Read() ([]byte, error) + Path() string + Type() VehicleType +} + +// Provider Type +const ( + Proxy ProviderType = iota + Rule +) + +// ProviderType defined +type ProviderType int + +func (pt ProviderType) String() string { + switch pt { + case Proxy: + return "Proxy" + case Rule: + return "Rule" + default: + return "Unknown" + } +} + +// Provider interface +type Provider interface { + Name() string + VehicleType() VehicleType + Type() ProviderType + Initial() error + Update() error +} + +// ProxyProvider interface +type ProxyProvider interface { + Provider + Proxies() []constant.Proxy + // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. + // Commonly used in DialContext and DialPacketConn + ProxiesWithTouch() []constant.Proxy + HealthCheck() +} + +// Rule Type +const ( + Domain RuleType = iota + IPCIDR + Classical +) + +// RuleType defined +type RuleType int + +func (rt RuleType) String() string { + switch rt { + case Domain: + return "Domain" + case IPCIDR: + return "IPCIDR" + case Classical: + return "Classical" + default: + return "Unknown" + } +} + +// RuleProvider interface +type RuleProvider interface { + Provider + Behavior() RuleType + Match(*constant.Metadata) bool + ShouldResolveIP() bool + AsRule(adaptor string) constant.Rule +} diff --git a/context/http.go b/context/http.go deleted file mode 100644 index 292f7d970..000000000 --- a/context/http.go +++ /dev/null @@ -1,47 +0,0 @@ -package context - -import ( - "net" - "net/http" - - C "github.com/Dreamacro/clash/constant" - - "github.com/gofrs/uuid" -) - -type HTTPContext struct { - id uuid.UUID - metadata *C.Metadata - conn net.Conn - req *http.Request -} - -func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext { - id, _ := uuid.NewV4() - return &HTTPContext{ - id: id, - metadata: metadata, - conn: conn, - req: req, - } -} - -// ID implement C.ConnContext ID -func (hc *HTTPContext) ID() uuid.UUID { - return hc.id -} - -// Metadata implement C.ConnContext Metadata -func (hc *HTTPContext) Metadata() *C.Metadata { - return hc.metadata -} - -// Conn implement C.ConnContext Conn -func (hc *HTTPContext) Conn() net.Conn { - return hc.conn -} - -// Request return the http request struct -func (hc *HTTPContext) Request() *http.Request { - return hc.req -} diff --git a/dns/client.go b/dns/client.go index 405b3a989..5cb1fe029 100644 --- a/dns/client.go +++ b/dns/client.go @@ -2,6 +2,7 @@ package dns import ( "context" + "crypto/tls" "fmt" "net" "strings" @@ -14,42 +15,46 @@ import ( type client struct { *D.Client - r *Resolver - port string - host string + r *Resolver + port string + host string + iface string } -func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { +func (c *client) Exchange(m *D.Msg) (*D.Msg, error) { return c.ExchangeContext(context.Background(), m) } -func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { - var ip net.IP +func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { + var ( + ip net.IP + err error + ) if c.r == nil { // a default ip dns - ip = net.ParseIP(c.host) + if ip = net.ParseIP(c.host); ip == nil { + return nil, fmt.Errorf("dns %s not a valid ip", c.host) + } } else { if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil { return nil, fmt.Errorf("use default dns resolve failed: %w", err) } } - d, err := dialer.Dialer() + network := "udp" + if strings.HasPrefix(c.Client.Net, "tcp") { + network = "tcp" + } + + options := []dialer.Option{} + if c.iface != "" { + options = append(options, dialer.WithInterface(c.iface)) + } + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...) if err != nil { return nil, err } - - if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil { - network := "udp" - if strings.HasPrefix(c.Client.Net, "tcp") { - network = "tcp" - } - if err := dialer.DialHook(d, network, ip); err != nil { - return nil, err - } - } - - c.Client.Dialer = d + defer conn.Close() // miekg/dns ExchangeContext doesn't respond to context cancel. // this is a workaround @@ -59,7 +64,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } ch := make(chan result, 1) go func() { - msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) + if strings.HasSuffix(c.Client.Net, "tls") { + conn = tls.Client(conn, c.Client.TLSConfig) + } + + msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ + Conn: conn, + UDPSize: c.Client.UDPSize, + TsigSecret: c.Client.TsigSecret, + TsigProvider: c.Client.TsigProvider, + }) + ch <- result{msg, err} }() diff --git a/dns/dhcp.go b/dns/dhcp.go new file mode 100644 index 000000000..f964cec8c --- /dev/null +++ b/dns/dhcp.go @@ -0,0 +1,147 @@ +package dns + +import ( + "bytes" + "context" + "net" + "sync" + "time" + + "github.com/Dreamacro/clash/component/dhcp" + "github.com/Dreamacro/clash/component/iface" + "github.com/Dreamacro/clash/component/resolver" + + D "github.com/miekg/dns" +) + +const ( + IfaceTTL = time.Second * 20 + DHCPTTL = time.Hour + DHCPTimeout = time.Minute +) + +type dhcpClient struct { + ifaceName string + + lock sync.Mutex + ifaceInvalidate time.Time + dnsInvalidate time.Time + + ifaceAddr *net.IPNet + done chan struct{} + resolver *Resolver + err error +} + +func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { + ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) + defer cancel() + + return d.ExchangeContext(ctx, m) +} + +func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + res, err := d.resolve(ctx) + if err != nil { + return nil, err + } + + return res.ExchangeContext(ctx, m) +} + +func (d *dhcpClient) resolve(ctx context.Context) (*Resolver, error) { + d.lock.Lock() + + invalidated, err := d.invalidate() + if err != nil { + d.err = err + } else if invalidated { + done := make(chan struct{}) + + d.done = done + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), DHCPTimeout) + defer cancel() + + var res *Resolver + dns, err := dhcp.ResolveDNSFromDHCP(ctx, d.ifaceName) + if err == nil { + nameserver := make([]NameServer, 0, len(dns)) + for _, item := range dns { + nameserver = append(nameserver, NameServer{ + Addr: net.JoinHostPort(item.String(), "53"), + Interface: d.ifaceName, + }) + } + + res = NewResolver(Config{ + Main: nameserver, + }) + } + + d.lock.Lock() + defer d.lock.Unlock() + + close(done) + + d.done = nil + d.resolver = res + d.err = err + }() + } + + d.lock.Unlock() + + for { + d.lock.Lock() + + res, err, done := d.resolver, d.err, d.done + + d.lock.Unlock() + + // initializing + if res == nil && err == nil { + select { + case <-done: + continue + case <-ctx.Done(): + return nil, ctx.Err() + } + } + + // dirty return + return res, err + } +} + +func (d *dhcpClient) invalidate() (bool, error) { + if time.Now().Before(d.ifaceInvalidate) { + return false, nil + } + + d.ifaceInvalidate = time.Now().Add(IfaceTTL) + + ifaceObj, err := iface.ResolveInterface(d.ifaceName) + if err != nil { + return false, err + } + + addr, err := ifaceObj.PickIPv4Addr(nil) + if err != nil { + return false, err + } + + if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) { + return false, nil + } + + d.dnsInvalidate = time.Now().Add(DHCPTTL) + d.ifaceAddr = addr + + return d.done == nil, nil +} + +func newDHCPClient(ifaceName string) *dhcpClient { + return &dhcpClient{ifaceName: ifaceName} +} diff --git a/dns/doh.go b/dns/doh.go index 247e0704d..943123551 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -3,8 +3,7 @@ package dns import ( "bytes" "context" - "crypto/tls" - "io/ioutil" + "io" "net" "net/http" @@ -29,13 +28,21 @@ func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { } func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { - req, err := dc.newRequest(m) + // https://datatracker.ietf.org/doc/html/rfc8484#section-4.1 + // In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request. + newM := *m + newM.Id = 0 + req, err := dc.newRequest(&newM) if err != nil { return nil, err } req = req.WithContext(ctx) - return dc.doRequest(req) + msg, err = dc.doRequest(req) + if err == nil { + msg.Id = m.Id + } + return } // newRequest returns a new DoH request given a dns.Msg. @@ -63,7 +70,7 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { } defer resp.Body.Close() - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -76,7 +83,6 @@ func newDoHClient(url string, r *Resolver) *dohClient { return &dohClient{ url: url, transport: &http.Transport{ - TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, ForceAttemptHTTP2: true, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { host, port, err := net.SplitHostPort(addr) diff --git a/dns/enhancer.go b/dns/enhancer.go index 0b2570059..76f0f2628 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -5,20 +5,21 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" + C "github.com/Dreamacro/clash/constant" ) type ResolverEnhancer struct { - mode EnhancedMode + mode C.DNSMode fakePool *fakeip.Pool mapping *cache.LruCache } func (h *ResolverEnhancer) FakeIPEnabled() bool { - return h.mode == FAKEIP + return h.mode == C.DNSFakeIP } func (h *ResolverEnhancer) MappingEnabled() bool { - return h.mode == FAKEIP || h.mode == MAPPING + return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping } func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool { @@ -67,7 +68,7 @@ func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { } if h.fakePool != nil && o.fakePool != nil { - h.fakePool.PatchFrom(o.fakePool) + h.fakePool.CloneFrom(o.fakePool) } } @@ -75,7 +76,7 @@ func NewEnhancer(cfg Config) *ResolverEnhancer { var fakePool *fakeip.Pool var mapping *cache.LruCache - if cfg.EnhancedMode != NORMAL { + if cfg.EnhancedMode != C.DNSNormal { fakePool = cfg.Pool mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)) } diff --git a/dns/filters.go b/dns/filters.go index 583883fab..30825a44a 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -2,6 +2,7 @@ package dns import ( "net" + "strings" "github.com/Dreamacro/clash/component/mmdb" "github.com/Dreamacro/clash/component/trie" @@ -11,11 +12,13 @@ type fallbackIPFilter interface { Match(net.IP) bool } -type geoipFilter struct{} +type geoipFilter struct { + code string +} func (gf *geoipFilter) Match(ip net.IP) bool { record, _ := mmdb.Instance().Country(ip) - return record.Country.IsoCode != "CN" && record.Country.IsoCode != "" + return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate() } type ipnetFilter struct { @@ -29,6 +32,7 @@ func (inf *ipnetFilter) Match(ip net.IP) bool { type fallbackDomainFilter interface { Match(domain string) bool } + type domainFilter struct { tree *trie.DomainTrie } diff --git a/dns/middleware.go b/dns/middleware.go index 782c0ef07..9d0da5968 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -8,14 +8,17 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/trie" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" ) -type handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) -type middleware func(next handler) handler +type ( + handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) + middleware func(next handler) handler +) func withHosts(hosts *trie.DomainTrie) middleware { return func(next handler) handler { @@ -105,7 +108,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { q := r.Question[0] host := strings.TrimRight(q.Name, ".") - if fakePool.LookupHost(host) { + if fakePool.ShouldSkipped(host) { return next(ctx, r) } @@ -176,11 +179,11 @@ func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler { middlewares = append(middlewares, withHosts(resolver.hosts)) } - if mapper.mode == FAKEIP { + if mapper.mode == C.DNSFakeIP { middlewares = append(middlewares, withFakeIP(mapper.fakePool)) } - if mapper.mode != NORMAL { + if mapper.mode != C.DNSNormal { middlewares = append(middlewares, withMapping(mapper.mapping)) } diff --git a/dns/resolver.go b/dns/resolver.go index 0db6855dd..91efbb8bc 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -2,7 +2,6 @@ package dns import ( "context" - "crypto/tls" "errors" "fmt" "math/rand" @@ -15,15 +14,12 @@ import ( "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/trie" + C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" "golang.org/x/sync/singleflight" ) -var ( - globalSessionCache = tls.NewLRUClientSessionCache(64) -) - type dnsClient interface { Exchange(m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) @@ -43,6 +39,7 @@ type Resolver struct { fallbackIPFilters []fallbackIPFilter group singleflight.Group lruCache *cache.LruCache + policy *trie.DomainTrie } // ResolveIP request with TypeA and TypeAAAA, priority return TypeA @@ -91,6 +88,11 @@ func (r *Resolver) shouldIPFallback(ip net.IP) bool { // Exchange a batch of dns request, and it use cache func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { + return r.ExchangeContext(context.Background(), m) +} + +// ExchangeContext a batch of dns request with context.Context, and it use cache +func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { if len(m.Question) == 0 { return nil, errors.New("should have one question at least") } @@ -102,17 +104,17 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { msg = cache.(*D.Msg).Copy() if expireTime.Before(now) { setMsgTTL(msg, uint32(1)) // Continue fetch - go r.exchangeWithoutCache(m) + go r.exchangeWithoutCache(ctx, m) } else { setMsgTTL(msg, uint32(time.Until(expireTime).Seconds())) } return } - return r.exchangeWithoutCache(m) + return r.exchangeWithoutCache(ctx, m) } // ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache -func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { +func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { q := m.Question[0] ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) { @@ -128,10 +130,13 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { isIPReq := isIPRequest(q) if isIPReq { - return r.ipExchange(m) + return r.ipExchange(ctx, m) } - return r.batchExchange(r.main, m) + if matched := r.matchPolicy(m); len(matched) != 0 { + return r.batchExchange(ctx, matched, m) + } + return r.batchExchange(ctx, r.main, m) }) if err == nil { @@ -144,8 +149,8 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { return } -func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { - fast, ctx := picker.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) +func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { + fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout) for _, client := range clients { r := client fast.Go(func() (interface{}, error) { @@ -172,6 +177,24 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err return } +func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient { + if r.policy == nil { + return nil + } + + domain := r.msgToDomain(m) + if domain == "" { + return nil + } + + record := r.policy.Search(domain) + if record == nil { + return nil + } + + return record.Data.([]dnsClient) +} + func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { if r.fallback == nil || len(r.fallbackDomainFilters) == 0 { return false @@ -192,16 +215,20 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { return false } -func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { +func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + if matched := r.matchPolicy(m); len(matched) != 0 { + res := <-r.asyncExchange(ctx, matched, m) + return res.Msg, res.Error + } onlyFallback := r.shouldOnlyQueryFallback(m) if onlyFallback { - res := <-r.asyncExchange(r.fallback, m) + res := <-r.asyncExchange(ctx, r.fallback, m) return res.Msg, res.Error } - msgCh := r.asyncExchange(r.main, m) + msgCh := r.asyncExchange(ctx, r.main, m) if r.fallback == nil { // directly return if no fallback servers are available res := <-msgCh @@ -209,7 +236,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { return } - fallbackMsg := r.asyncExchange(r.fallback, m) + fallbackMsg := r.asyncExchange(ctx, r.fallback, m) res := <-msgCh if res.Error == nil { if ips := msgToIP(res.Msg); len(ips) != 0 { @@ -265,34 +292,37 @@ func (r *Resolver) msgToDomain(msg *D.Msg) string { return "" } -func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { +func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result { ch := make(chan *result, 1) go func() { - res, err := r.batchExchange(client, msg) + res, err := r.batchExchange(ctx, client, msg) ch <- &result{Msg: res, Error: err} }() return ch } type NameServer struct { - Net string - Addr string + Net string + Addr string + Interface string } type FallbackFilter struct { - GeoIP bool - IPCIDR []*net.IPNet - Domain []string + GeoIP bool + GeoIPCode string + IPCIDR []*net.IPNet + Domain []string } type Config struct { Main, Fallback []NameServer Default []NameServer IPv6 bool - EnhancedMode EnhancedMode + EnhancedMode C.DNSMode FallbackFilter FallbackFilter Pool *fakeip.Pool Hosts *trie.DomainTrie + Policy map[string]NameServer } func NewResolver(config Config) *Resolver { @@ -312,9 +342,18 @@ func NewResolver(config Config) *Resolver { r.fallback = transform(config.Fallback, defaultResolver) } + if len(config.Policy) != 0 { + r.policy = trie.New() + for domain, nameserver := range config.Policy { + r.policy.Insert(domain, transform([]NameServer{nameserver}, defaultResolver)) + } + } + fallbackIPFilters := []fallbackIPFilter{} if config.FallbackFilter.GeoIP { - fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{}) + fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{ + code: config.FallbackFilter.GeoIPCode, + }) } for _, ipnet := range config.FallbackFilter.IPCIDR { fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet}) diff --git a/dns/server.go b/dns/server.go index 84ff0bac4..7abdd4d0f 100644 --- a/dns/server.go +++ b/dns/server.go @@ -30,6 +30,7 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { D.HandleFailed(w, r) return } + msg.Compress = true w.WriteMsg(msg) } diff --git a/dns/util.go b/dns/util.go index c2bb11d86..d11870f81 100644 --- a/dns/util.go +++ b/dns/util.go @@ -2,8 +2,6 @@ package dns import ( "crypto/tls" - "encoding/json" - "errors" "net" "time" @@ -13,72 +11,6 @@ import ( D "github.com/miekg/dns" ) -var ( - // EnhancedModeMapping is a mapping for EnhancedMode enum - EnhancedModeMapping = map[string]EnhancedMode{ - NORMAL.String(): NORMAL, - FAKEIP.String(): FAKEIP, - MAPPING.String(): MAPPING, - } -) - -const ( - NORMAL EnhancedMode = iota - FAKEIP - MAPPING -) - -type EnhancedMode int - -// UnmarshalYAML unserialize EnhancedMode with yaml -func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error { - var tp string - if err := unmarshal(&tp); err != nil { - return err - } - mode, exist := EnhancedModeMapping[tp] - if !exist { - return errors.New("invalid mode") - } - *e = mode - return nil -} - -// MarshalYAML serialize EnhancedMode with yaml -func (e EnhancedMode) MarshalYAML() (interface{}, error) { - return e.String(), nil -} - -// UnmarshalJSON unserialize EnhancedMode with json -func (e *EnhancedMode) UnmarshalJSON(data []byte) error { - var tp string - json.Unmarshal(data, &tp) - mode, exist := EnhancedModeMapping[tp] - if !exist { - return errors.New("invalid mode") - } - *e = mode - return nil -} - -// MarshalJSON serialize EnhancedMode with json -func (e EnhancedMode) MarshalJSON() ([]byte, error) { - return json.Marshal(e.String()) -} - -func (e EnhancedMode) String() string { - switch e { - case NORMAL: - return "normal" - case FAKEIP: - return "fake-ip" - case MAPPING: - return "redir-host" - default: - return "unknown" - } -} - func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) { var ttl uint32 switch { @@ -117,9 +49,13 @@ func isIPRequest(q D.Question) bool { func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret := []dnsClient{} for _, s := range servers { - if s.Net == "https" { + switch s.Net { + case "https": ret = append(ret, newDoHClient(s.Addr, resolver)) continue + case "dhcp": + ret = append(ret, newDHCPClient(s.Addr)) + continue } host, port, _ := net.SplitHostPort(s.Addr) @@ -127,7 +63,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { Client: &D.Client{ Net: s.Net, TLSConfig: &tls.Config{ - ClientSessionCache: globalSessionCache, // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 NextProtos: []string{"dns"}, ServerName: host, @@ -135,9 +70,10 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { UDPSize: 4096, Timeout: 5 * time.Second, }, - port: port, - host: host, - r: resolver, + port: port, + host: host, + iface: s.Interface, + r: resolver, }) } return ret diff --git a/go.mod b/go.mod index 17056c628..e2ac4f2d9 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,34 @@ module github.com/Dreamacro/clash -go 1.16 +go 1.17 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.7 - github.com/go-chi/chi/v5 v5.0.3 + github.com/go-chi/chi/v5 v5.0.5 github.com/go-chi/cors v1.2.0 github.com/go-chi/render v1.0.1 - github.com/gofrs/uuid v4.0.0+incompatible + github.com/gofrs/uuid v4.1.0+incompatible github.com/gorilla/websocket v1.4.2 - github.com/miekg/dns v1.1.42 + github.com/insomniacslk/dhcp v0.0.0-20211026125128-ad197bcd36fd + github.com/miekg/dns v1.1.43 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 - golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf - golang.org/x/net v0.0.0-20210508051633-16afe75a6701 + go.etcd.io/bbolt v1.3.6 + go.uber.org/atomic v1.9.0 + go.uber.org/automaxprocs v1.4.0 + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + golang.org/x/net v0.0.0-20211105192438-b53810dc28af golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 + golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/oschwald/maxminddb-golang v1.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect + golang.org/x/text v0.3.6 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/go.sum b/go.sum index 58dcc672b..6ef69f45f 100644 --- a/go.sum +++ b/go.sum @@ -3,18 +3,45 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr 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/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= -github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/go-chi/chi/v5 v5.0.5 h1:l3RJ8T8TAqLsXFfah+RA6N4pydMbPwSdvNM+AFWvLUM= +github.com/go-chi/chi/v5 v5.0.5/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/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/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk= +github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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/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/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/insomniacslk/dhcp v0.0.0-20211026125128-ad197bcd36fd h1:jupbuQFZtwOBg/3EmK91/rGaYFkqCb9bwHOnwn7Cav0= +github.com/insomniacslk/dhcp v0.0.0-20211026125128-ad197bcd36fd/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 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= @@ -23,36 +50,76 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0= +go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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-20190419010253-1f3472d942ba/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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/net v0.0.0-20211105192438-b53810dc28af h1:SMeNJG/vclJ5wyBBd4xupMsSJIHTd1coW9g7q6KOjmY= +golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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-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-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 h1:G2DDmludOQZoWbpCr7OKDxnl478ZBGMcOhrv+ooX/Q4= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/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/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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/yaml.v2 v2.2.2/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= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index bea2a1abd..59f948817 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,37 +2,35 @@ package executor import ( "fmt" - "io/ioutil" "os" "sync" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/outboundgroup" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter" + "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/profile" "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/dns" + P "github.com/Dreamacro/clash/listener" + authStore "github.com/Dreamacro/clash/listener/auth" "github.com/Dreamacro/clash/log" - P "github.com/Dreamacro/clash/proxy" - authStore "github.com/Dreamacro/clash/proxy/auth" "github.com/Dreamacro/clash/tunnel" ) -var ( - mux sync.Mutex -) +var mux sync.Mutex func readConfig(path string) ([]byte, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { return nil, err } @@ -70,13 +68,13 @@ func ApplyConfig(cfg *config.Config, force bool) { defer mux.Unlock() updateUsers(cfg.Users) - updateGeneral(cfg.General, force) updateProxies(cfg.Proxies, cfg.Providers) updateRules(cfg.Rules) - updateDNS(cfg.DNS) updateHosts(cfg.Hosts) - updateExperimental(cfg) updateProfile(cfg) + updateGeneral(cfg.General, force) + updateDNS(cfg.DNS) + updateExperimental(cfg) } func GetGeneral() *config.General { @@ -123,11 +121,13 @@ func updateDNS(c *config.DNS) { Pool: c.FakeIPRange, Hosts: c.Hosts, FallbackFilter: dns.FallbackFilter{ - GeoIP: c.FallbackFilter.GeoIP, - IPCIDR: c.FallbackFilter.IPCIDR, - Domain: c.FallbackFilter.Domain, + GeoIP: c.FallbackFilter.GeoIP, + GeoIPCode: c.FallbackFilter.GeoIPCode, + IPCIDR: c.FallbackFilter.IPCIDR, + Domain: c.FallbackFilter.Domain, }, Default: c.DefaultNameserver, + Policy: c.NameServerPolicy, } r := dns.NewResolver(cfg) @@ -168,13 +168,9 @@ func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 - if general.Interface != "" { - dialer.DialHook = dialer.DialerWithInterface(general.Interface) - dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface) - } else { - dialer.DialHook = nil - dialer.ListenPacketHook = nil - } + dialer.DefaultInterface.Store(general.Interface) + + iface.FlushCache() if !force { return @@ -186,24 +182,27 @@ func updateGeneral(general *config.General, force bool) { bindAddress := general.BindAddress P.SetBindAddress(bindAddress) - if err := P.ReCreateHTTP(general.Port); err != nil { + tcpIn := tunnel.TCPIn() + udpIn := tunnel.UDPIn() + + if err := P.ReCreateHTTP(general.Port, tcpIn); err != nil { log.Errorln("Start HTTP server error: %s", err.Error()) } - if err := P.ReCreateSocks(general.SocksPort); err != nil { - log.Errorln("Start SOCKS5 server error: %s", err.Error()) + if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil { + log.Errorln("Start SOCKS server error: %s", err.Error()) } - if err := P.ReCreateRedir(general.RedirPort); err != nil { + if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil { log.Errorln("Start Redir server error: %s", err.Error()) } - if err := P.ReCreateTProxy(general.TProxyPort); err != nil { + if err := P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn); err != nil { log.Errorln("Start TProxy server error: %s", err.Error()) } - if err := P.ReCreateMixed(general.MixedPort); err != nil { - log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) + if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil { + log.Errorln("Start Mixed(http and socks) server error: %s", err.Error()) } } @@ -231,7 +230,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) { } for name, proxy := range proxies { - outbound, ok := proxy.(*outbound.Proxy) + outbound, ok := proxy.(*adapter.Proxy) if !ok { continue } diff --git a/hub/route/configs.go b/hub/route/configs.go index 84dd10a6b..48cb95eda 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -6,9 +6,10 @@ import ( "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/config" + "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub/executor" + P "github.com/Dreamacro/clash/listener" "github.com/Dreamacro/clash/log" - P "github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi/v5" @@ -66,11 +67,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { } ports := P.GetPorts() - P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) - P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) - P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) - P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort)) - P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) + + tcpIn := tunnel.TCPIn() + udpIn := tunnel.UDPIn() + + P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn) + P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn) + P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn) + P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn) + P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn) if general.Mode != nil { tunnel.SetMode(*general.Mode) @@ -112,6 +117,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { return } } else { + if req.Path == "" { + req.Path = constant.Path.Config() + } if !filepath.IsAbs(req.Path) { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("path is not a absolute path")) diff --git a/hub/route/provider.go b/hub/route/provider.go index 902f13aff..0b599445a 100644 --- a/hub/route/provider.go +++ b/hub/route/provider.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi/v5" diff --git a/hub/route/proxies.go b/hub/route/proxies.go index e541223dd..bba9e2a80 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -7,8 +7,8 @@ import ( "strconv" "time" - "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/adapters/outboundgroup" + "github.com/Dreamacro/clash/adapter" + "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/component/profile/cachefile" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" @@ -78,7 +78,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { return } - proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy) + proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy) selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector) if !ok { render.Status(r, http.StatusBadRequest) diff --git a/hub/route/server.go b/hub/route/server.go index 98dc3588d..e01696bed 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -3,6 +3,7 @@ package route import ( "bytes" "encoding/json" + "net" "net/http" "strings" "time" @@ -81,10 +82,15 @@ func Start(addr string, secret string) { }) } - log.Infoln("RESTful API listening at: %s", addr) - err := http.ListenAndServe(addr, r) + l, err := net.Listen("tcp", addr) if err != nil { - log.Errorln("External controller error: %s", err.Error()) + log.Errorln("External controller listen error: %s", err) + return + } + serverAddr = l.Addr().String() + log.Infoln("RESTful API listening at: %s", serverAddr) + if err = http.Serve(l, r); err != nil { + log.Errorln("External controller serve error: %s", err) } } diff --git a/proxy/auth/auth.go b/listener/auth/auth.go similarity index 83% rename from proxy/auth/auth.go rename to listener/auth/auth.go index 2c29e186e..70473114d 100644 --- a/proxy/auth/auth.go +++ b/listener/auth/auth.go @@ -4,9 +4,7 @@ import ( "github.com/Dreamacro/clash/component/auth" ) -var ( - authenticator auth.Authenticator -) +var authenticator auth.Authenticator func Authenticator() auth.Authenticator { return authenticator diff --git a/listener/http/client.go b/listener/http/client.go new file mode 100644 index 000000000..873a9a3c9 --- /dev/null +++ b/listener/http/client.go @@ -0,0 +1,44 @@ +package http + +import ( + "context" + "errors" + "net" + "net/http" + "time" + + "github.com/Dreamacro/clash/adapter/inbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" +) + +func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client { + return &http.Client{ + Transport: &http.Transport{ + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + DialContext: func(context context.Context, network, address string) (net.Conn, error) { + if network != "tcp" && network != "tcp4" && network != "tcp6" { + return nil, errors.New("unsupported network " + network) + } + + dstAddr := socks5.ParseAddr(address) + if dstAddr == nil { + return nil, socks5.ErrAddressNotSupported + } + + left, right := net.Pipe() + + in <- inbound.NewHTTP(dstAddr, source, right) + + return left, nil + }, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} diff --git a/listener/http/hack.go b/listener/http/hack.go new file mode 100644 index 000000000..c33eb6f1b --- /dev/null +++ b/listener/http/hack.go @@ -0,0 +1,10 @@ +package http + +import ( + "bufio" + "net/http" + _ "unsafe" +) + +//go:linkname ReadRequest net/http.readRequest +func ReadRequest(b *bufio.Reader) (req *http.Request, err error) diff --git a/listener/http/proxy.go b/listener/http/proxy.go new file mode 100644 index 000000000..23a73739a --- /dev/null +++ b/listener/http/proxy.go @@ -0,0 +1,131 @@ +package http + +import ( + "fmt" + "net" + "net/http" + "strings" + "time" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/common/cache" + N "github.com/Dreamacro/clash/common/net" + C "github.com/Dreamacro/clash/constant" + authStore "github.com/Dreamacro/clash/listener/auth" + "github.com/Dreamacro/clash/log" +) + +func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) { + client := newClient(c.RemoteAddr(), in) + defer client.CloseIdleConnections() + + conn := N.NewBufferedConn(c) + + keepAlive := true + trusted := cache == nil // disable authenticate if cache is nil + + for keepAlive { + request, err := ReadRequest(conn.Reader()) + if err != nil { + break + } + + request.RemoteAddr = conn.RemoteAddr().String() + + keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" + + var resp *http.Response + + if !trusted { + resp = authenticate(request, cache) + + trusted = resp == nil + } + + if trusted { + if request.Method == http.MethodConnect { + // Manual writing to support CONNECT for http 1.0 (workaround for uplay client) + if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil { + break // close connection + } + + in <- inbound.NewHTTPS(request, conn) + + return // hijack connection + } + + host := request.Header.Get("Host") + if host != "" { + request.Host = host + } + + request.RequestURI = "" + + removeHopByHopHeaders(request.Header) + removeExtraHTTPHostPort(request) + + if request.URL.Scheme == "" || request.URL.Host == "" { + resp = responseWith(request, http.StatusBadRequest) + } else { + resp, err = client.Do(request) + if err != nil { + resp = responseWith(request, http.StatusBadGateway) + } + } + + removeHopByHopHeaders(resp.Header) + } + + if keepAlive { + resp.Header.Set("Proxy-Connection", "keep-alive") + resp.Header.Set("Connection", "keep-alive") + resp.Header.Set("Keep-Alive", "timeout=4") + } + + resp.Close = !keepAlive + + err = resp.Write(conn) + if err != nil { + break // close connection + } + } + + conn.Close() +} + +func authenticate(request *http.Request, cache *cache.Cache) *http.Response { + authenticator := authStore.Authenticator() + if authenticator != nil { + credential := parseBasicProxyAuthorization(request) + if credential == "" { + resp := responseWith(request, http.StatusProxyAuthRequired) + resp.Header.Set("Proxy-Authenticate", "Basic") + return resp + } + + var authed interface{} + if authed = cache.Get(credential); authed == nil { + user, pass, err := decodeBasicProxyAuthorization(credential) + authed = err == nil && authenticator.Verify(user, pass) + cache.Put(credential, authed, time.Minute) + } + if !authed.(bool) { + log.Infoln("Auth failed from %s", request.RemoteAddr) + + return responseWith(request, http.StatusForbidden) + } + } + + return nil +} + +func responseWith(request *http.Request, statusCode int) *http.Response { + return &http.Response{ + StatusCode: statusCode, + Status: http.StatusText(statusCode), + Proto: request.Proto, + ProtoMajor: request.ProtoMajor, + ProtoMinor: request.ProtoMinor, + Header: http.Header{}, + } +} diff --git a/listener/http/server.go b/listener/http/server.go new file mode 100644 index 000000000..bfdd9f1b6 --- /dev/null +++ b/listener/http/server.go @@ -0,0 +1,66 @@ +package http + +import ( + "net" + "time" + + "github.com/Dreamacro/clash/common/cache" + C "github.com/Dreamacro/clash/constant" +) + +type Listener struct { + listener net.Listener + addr string + closed bool +} + +// RawAddress implements C.Listener +func (l *Listener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *Listener) Address() string { + return l.listener.Addr().String() +} + +// Close implements C.Listener +func (l *Listener) Close() error { + l.closed = true + return l.listener.Close() +} + +func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + return NewWithAuthenticate(addr, in, true) +} + +func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + var c *cache.Cache + if authenticate { + c = cache.New(time.Second * 30) + } + + hl := &Listener{ + listener: l, + addr: addr, + } + go func() { + for { + conn, err := hl.listener.Accept() + if err != nil { + if hl.closed { + break + } + continue + } + go HandleConn(conn, in, c) + } + }() + + return hl, nil +} diff --git a/listener/http/utils.go b/listener/http/utils.go new file mode 100644 index 000000000..177607991 --- /dev/null +++ b/listener/http/utils.go @@ -0,0 +1,74 @@ +package http + +import ( + "encoding/base64" + "errors" + "net" + "net/http" + "strings" +) + +// removeHopByHopHeaders remove hop-by-hop header +func removeHopByHopHeaders(header http.Header) { + // Strip hop-by-hop header based on RFC: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 + // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do + + header.Del("Proxy-Connection") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") + header.Del("TE") + header.Del("Trailers") + header.Del("Transfer-Encoding") + header.Del("Upgrade") + + connections := header.Get("Connection") + header.Del("Connection") + if len(connections) == 0 { + return + } + for _, h := range strings.Split(connections, ",") { + header.Del(strings.TrimSpace(h)) + } +} + +// removeExtraHTTPHostPort remove extra host port (example.com:80 --> example.com) +// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com) +func removeExtraHTTPHostPort(req *http.Request) { + host := req.Host + if host == "" { + host = req.URL.Host + } + + if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" { + host = pHost + } + + req.Host = host + req.URL.Host = host +} + +// parseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential +func parseBasicProxyAuthorization(request *http.Request) string { + value := request.Header.Get("Proxy-Authorization") + if !strings.HasPrefix(value, "Basic ") { + return "" + } + + return value[6:] // value[len("Basic "):] +} + +// decodeBasicProxyAuthorization decode base64-encoded credential +func decodeBasicProxyAuthorization(credential string) (string, string, error) { + plain, err := base64.StdEncoding.DecodeString(credential) + if err != nil { + return "", "", err + } + + login := strings.Split(string(plain), ":") + if len(login) != 2 { + return "", "", errors.New("invalid login") + } + + return login[0], login[1], nil +} diff --git a/proxy/listener.go b/listener/listener.go similarity index 65% rename from proxy/listener.go rename to listener/listener.go index ec9b69f9d..d20af99db 100644 --- a/proxy/listener.go +++ b/listener/listener.go @@ -6,26 +6,29 @@ import ( "strconv" "sync" + "github.com/Dreamacro/clash/adapter/inbound" + 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/log" - "github.com/Dreamacro/clash/proxy/http" - "github.com/Dreamacro/clash/proxy/mixed" - "github.com/Dreamacro/clash/proxy/redir" - "github.com/Dreamacro/clash/proxy/socks" ) var ( allowLan = false bindAddress = "*" - socksListener *socks.SockListener - socksUDPListener *socks.SockUDPListener - httpListener *http.HTTPListener - redirListener *redir.RedirListener - redirUDPListener *redir.RedirUDPListener - tproxyListener *redir.TProxyListener - tproxyUDPListener *redir.RedirUDPListener - mixedListener *mixed.MixedListener - mixedUDPLister *socks.SockUDPListener + socksListener *socks.Listener + socksUDPListener *socks.UDPListener + httpListener *http.Listener + redirListener *redir.Listener + redirUDPListener *tproxy.UDPListener + tproxyListener *tproxy.Listener + tproxyUDPListener *tproxy.UDPListener + mixedListener *mixed.Listener + mixedUDPLister *socks.UDPListener // lock for recreate function socksMux sync.Mutex @@ -59,14 +62,14 @@ func SetBindAddress(host string) { bindAddress = host } -func ReCreateHTTP(port int) error { +func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) error { httpMux.Lock() defer httpMux.Unlock() addr := genAddr(bindAddress, port, allowLan) if httpListener != nil { - if httpListener.Address() == addr { + if httpListener.RawAddress() == addr { return nil } httpListener.Close() @@ -78,15 +81,16 @@ func ReCreateHTTP(port int) error { } var err error - httpListener, err = http.NewHTTPProxy(addr) + httpListener, err = http.New(addr, tcpIn) if err != nil { return err } + log.Infoln("HTTP proxy listening at: %s", httpListener.Address()) return nil } -func ReCreateSocks(port int) error { +func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error { socksMux.Lock() defer socksMux.Unlock() @@ -96,7 +100,7 @@ func ReCreateSocks(port int) error { shouldUDPIgnore := false if socksListener != nil { - if socksListener.Address() != addr { + if socksListener.RawAddress() != addr { socksListener.Close() socksListener = nil } else { @@ -105,7 +109,7 @@ func ReCreateSocks(port int) error { } if socksUDPListener != nil { - if socksUDPListener.Address() != addr { + if socksUDPListener.RawAddress() != addr { socksUDPListener.Close() socksUDPListener = nil } else { @@ -121,12 +125,12 @@ func ReCreateSocks(port int) error { return nil } - tcpListener, err := socks.NewSocksProxy(addr) + tcpListener, err := socks.New(addr, tcpIn) if err != nil { return err } - udpListener, err := socks.NewSocksUDPProxy(addr) + udpListener, err := socks.NewUDP(addr, udpIn) if err != nil { tcpListener.Close() return err @@ -135,17 +139,18 @@ func ReCreateSocks(port int) error { socksListener = tcpListener socksUDPListener = udpListener + log.Infoln("SOCKS proxy listening at: %s", socksListener.Address()) return nil } -func ReCreateRedir(port int) error { +func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error { redirMux.Lock() defer redirMux.Unlock() addr := genAddr(bindAddress, port, allowLan) if redirListener != nil { - if redirListener.Address() == addr { + if redirListener.RawAddress() == addr { return nil } redirListener.Close() @@ -153,7 +158,7 @@ func ReCreateRedir(port int) error { } if redirUDPListener != nil { - if redirUDPListener.Address() == addr { + if redirUDPListener.RawAddress() == addr { return nil } redirUDPListener.Close() @@ -165,27 +170,28 @@ func ReCreateRedir(port int) error { } var err error - redirListener, err = redir.NewRedirProxy(addr) + redirListener, err = redir.New(addr, tcpIn) if err != nil { return err } - redirUDPListener, err = redir.NewRedirUDPProxy(addr) + redirUDPListener, err = tproxy.NewUDP(addr, udpIn) if err != nil { log.Warnln("Failed to start Redir UDP Listener: %s", err) } + log.Infoln("Redirect proxy listening at: %s", redirListener.Address()) return nil } -func ReCreateTProxy(port int) error { +func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error { tproxyMux.Lock() defer tproxyMux.Unlock() addr := genAddr(bindAddress, port, allowLan) if tproxyListener != nil { - if tproxyListener.Address() == addr { + if tproxyListener.RawAddress() == addr { return nil } tproxyListener.Close() @@ -193,7 +199,7 @@ func ReCreateTProxy(port int) error { } if tproxyUDPListener != nil { - if tproxyUDPListener.Address() == addr { + if tproxyUDPListener.RawAddress() == addr { return nil } tproxyUDPListener.Close() @@ -205,20 +211,21 @@ func ReCreateTProxy(port int) error { } var err error - tproxyListener, err = redir.NewTProxy(addr) + tproxyListener, err = tproxy.New(addr, tcpIn) if err != nil { return err } - tproxyUDPListener, err = redir.NewRedirUDPProxy(addr) + tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn) if err != nil { log.Warnln("Failed to start TProxy UDP Listener: %s", err) } + log.Infoln("TProxy server listening at: %s", tproxyListener.Address()) return nil } -func ReCreateMixed(port int) error { +func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error { mixedMux.Lock() defer mixedMux.Unlock() @@ -228,7 +235,7 @@ func ReCreateMixed(port int) error { shouldUDPIgnore := false if mixedListener != nil { - if mixedListener.Address() != addr { + if mixedListener.RawAddress() != addr { mixedListener.Close() mixedListener = nil } else { @@ -236,7 +243,7 @@ func ReCreateMixed(port int) error { } } if mixedUDPLister != nil { - if mixedUDPLister.Address() != addr { + if mixedUDPLister.RawAddress() != addr { mixedUDPLister.Close() mixedUDPLister = nil } else { @@ -253,17 +260,18 @@ func ReCreateMixed(port int) error { } var err error - mixedListener, err = mixed.NewMixedProxy(addr) + mixedListener, err = mixed.New(addr, tcpIn) if err != nil { return err } - mixedUDPLister, err = socks.NewSocksUDPProxy(addr) + mixedUDPLister, err = socks.NewUDP(addr, udpIn) if err != nil { mixedListener.Close() return err } + log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address()) return nil } diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go new file mode 100644 index 000000000..8fd4f990e --- /dev/null +++ b/listener/mixed/mixed.go @@ -0,0 +1,81 @@ +package mixed + +import ( + "net" + "time" + + "github.com/Dreamacro/clash/common/cache" + N "github.com/Dreamacro/clash/common/net" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/http" + "github.com/Dreamacro/clash/listener/socks" + "github.com/Dreamacro/clash/transport/socks4" + "github.com/Dreamacro/clash/transport/socks5" +) + +type Listener struct { + listener net.Listener + addr string + cache *cache.Cache + closed bool +} + +// RawAddress implements C.Listener +func (l *Listener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *Listener) Address() string { + return l.listener.Addr().String() +} + +// Close implements C.Listener +func (l *Listener) Close() error { + l.closed = true + return l.listener.Close() +} + +func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + ml := &Listener{ + listener: l, + addr: addr, + cache: cache.New(30 * time.Second), + } + go func() { + for { + c, err := ml.listener.Accept() + if err != nil { + if ml.closed { + break + } + continue + } + go handleConn(c, in, ml.cache) + } + }() + + return ml, nil +} + +func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) { + bufConn := N.NewBufferedConn(conn) + head, err := bufConn.Peek(1) + if err != nil { + return + } + + switch head[0] { + case socks4.Version: + socks.HandleSocks4(bufConn, in) + case socks5.Version: + socks.HandleSocks5(bufConn, in) + default: + http.HandleConn(bufConn, in, cache) + } +} diff --git a/listener/redir/tcp.go b/listener/redir/tcp.go new file mode 100644 index 000000000..15c98a8f3 --- /dev/null +++ b/listener/redir/tcp.go @@ -0,0 +1,66 @@ +package redir + +import ( + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + C "github.com/Dreamacro/clash/constant" +) + +type Listener struct { + listener net.Listener + addr string + closed bool +} + +// RawAddress implements C.Listener +func (l *Listener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *Listener) Address() string { + return l.listener.Addr().String() +} + +// Close implements C.Listener +func (l *Listener) Close() error { + l.closed = true + return l.listener.Close() +} + +func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + rl := &Listener{ + listener: l, + addr: addr, + } + + go func() { + for { + c, err := l.Accept() + if err != nil { + if rl.closed { + break + } + continue + } + go handleRedir(c, in) + } + }() + + return rl, nil +} + +func handleRedir(conn net.Conn, in chan<- C.ConnContext) { + target, err := parserPacket(conn) + if err != nil { + conn.Close() + return + } + conn.(*net.TCPConn).SetKeepAlive(true) + in <- inbound.NewSocket(target, conn, C.REDIR) +} diff --git a/proxy/redir/tcp_darwin.go b/listener/redir/tcp_darwin.go similarity index 97% rename from proxy/redir/tcp_darwin.go rename to listener/redir/tcp_darwin.go index 4e9ade1e5..5a2f331c3 100644 --- a/proxy/redir/tcp_darwin.go +++ b/listener/redir/tcp_darwin.go @@ -5,7 +5,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/transport/socks5" ) func parserPacket(c net.Conn) (socks5.Addr, error) { diff --git a/proxy/redir/tcp_freebsd.go b/listener/redir/tcp_freebsd.go similarity index 96% rename from proxy/redir/tcp_freebsd.go rename to listener/redir/tcp_freebsd.go index f9cdf5a6a..12c4ba6ae 100644 --- a/proxy/redir/tcp_freebsd.go +++ b/listener/redir/tcp_freebsd.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/transport/socks5" ) const ( diff --git a/proxy/redir/tcp_linux.go b/listener/redir/tcp_linux.go similarity index 96% rename from proxy/redir/tcp_linux.go rename to listener/redir/tcp_linux.go index ac3a455c3..e802319cd 100644 --- a/proxy/redir/tcp_linux.go +++ b/listener/redir/tcp_linux.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/transport/socks5" ) const ( diff --git a/proxy/redir/tcp_linux_386.go b/listener/redir/tcp_linux_386.go similarity index 100% rename from proxy/redir/tcp_linux_386.go rename to listener/redir/tcp_linux_386.go diff --git a/proxy/redir/tcp_linux_other.go b/listener/redir/tcp_linux_other.go similarity index 91% rename from proxy/redir/tcp_linux_other.go rename to listener/redir/tcp_linux_other.go index 95472823c..3299843d0 100644 --- a/proxy/redir/tcp_linux_other.go +++ b/listener/redir/tcp_linux_other.go @@ -1,3 +1,4 @@ +//go:build linux && !386 // +build linux,!386 package redir diff --git a/proxy/redir/tcp_other.go b/listener/redir/tcp_other.go similarity index 68% rename from proxy/redir/tcp_other.go rename to listener/redir/tcp_other.go index 21743ec08..592e9584f 100644 --- a/proxy/redir/tcp_other.go +++ b/listener/redir/tcp_other.go @@ -1,3 +1,4 @@ +//go:build !darwin && !linux && !freebsd // +build !darwin,!linux,!freebsd package redir @@ -6,7 +7,7 @@ import ( "errors" "net" - "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/transport/socks5" ) func parserPacket(conn net.Conn) (socks5.Addr, error) { diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go new file mode 100644 index 000000000..29016f5b1 --- /dev/null +++ b/listener/socks/tcp.go @@ -0,0 +1,108 @@ +package socks + +import ( + "io" + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" + C "github.com/Dreamacro/clash/constant" + authStore "github.com/Dreamacro/clash/listener/auth" + "github.com/Dreamacro/clash/transport/socks4" + "github.com/Dreamacro/clash/transport/socks5" +) + +type Listener struct { + listener net.Listener + addr string + closed bool +} + +// RawAddress implements C.Listener +func (l *Listener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *Listener) Address() string { + return l.listener.Addr().String() +} + +// Close implements C.Listener +func (l *Listener) Close() error { + l.closed = true + return l.listener.Close() +} + +func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + sl := &Listener{ + listener: l, + addr: addr, + } + go func() { + for { + c, err := l.Accept() + if err != nil { + if sl.closed { + break + } + continue + } + go handleSocks(c, in) + } + }() + + return sl, nil +} + +func handleSocks(conn net.Conn, in chan<- C.ConnContext) { + bufConn := N.NewBufferedConn(conn) + head, err := bufConn.Peek(1) + if err != nil { + conn.Close() + return + } + + switch head[0] { + case socks4.Version: + HandleSocks4(bufConn, in) + case socks5.Version: + HandleSocks5(bufConn, in) + default: + conn.Close() + } +} + +func HandleSocks4(conn net.Conn, in chan<- C.ConnContext) { + addr, _, err := socks4.ServerHandshake(conn, authStore.Authenticator()) + if err != nil { + conn.Close() + return + } + if c, ok := conn.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + in <- inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4) +} + +func HandleSocks5(conn net.Conn, in chan<- C.ConnContext) { + target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) + if err != nil { + conn.Close() + return + } + if c, ok := conn.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + if command == socks5.CmdUDPAssociate { + defer conn.Close() + io.Copy(io.Discard, conn) + return + } + in <- inbound.NewSocket(target, conn, C.SOCKS5) +} diff --git a/listener/socks/udp.go b/listener/socks/udp.go new file mode 100644 index 000000000..8bc439fb4 --- /dev/null +++ b/listener/socks/udp.go @@ -0,0 +1,85 @@ +package socks + +import ( + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/common/sockopt" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/socks5" +) + +type UDPListener struct { + packetConn net.PacketConn + addr string + closed bool +} + +// RawAddress implements C.Listener +func (l *UDPListener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *UDPListener) Address() string { + return l.packetConn.LocalAddr().String() +} + +// Close implements C.Listener +func (l *UDPListener) Close() error { + l.closed = true + return l.packetConn.Close() +} + +func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { + l, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + + if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil { + log.Warnln("Failed to Reuse UDP Address: %s", err) + } + + sl := &UDPListener{ + packetConn: l, + addr: addr, + } + go func() { + for { + buf := pool.Get(pool.UDPBufferSize) + n, remoteAddr, err := l.ReadFrom(buf) + if err != nil { + pool.Put(buf) + if sl.closed { + break + } + continue + } + handleSocksUDP(l, in, buf[:n], remoteAddr) + } + }() + + return sl, nil +} + +func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { + target, payload, err := socks5.DecodeUDPPacket(buf) + if err != nil { + // Unresolved UDP packet, return buffer to the pool + pool.Put(buf) + return + } + packet := &packet{ + pc: pc, + rAddr: addr, + payload: payload, + bufRef: buf, + } + select { + case in <- inbound.NewPacket(target, packet, C.SOCKS5): + default: + } +} diff --git a/proxy/socks/utils.go b/listener/socks/utils.go similarity index 93% rename from proxy/socks/utils.go rename to listener/socks/utils.go index 797e0a220..28dfef723 100644 --- a/proxy/socks/utils.go +++ b/listener/socks/utils.go @@ -4,7 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/transport/socks5" ) type packet struct { diff --git a/proxy/redir/utils.go b/listener/tproxy/packet.go similarity index 97% rename from proxy/redir/utils.go rename to listener/tproxy/packet.go index 58e30b0ce..8aa3e9bf4 100644 --- a/proxy/redir/utils.go +++ b/listener/tproxy/packet.go @@ -1,4 +1,4 @@ -package redir +package tproxy import ( "net" diff --git a/proxy/redir/tproxy_linux.go b/listener/tproxy/setsockopt_linux.go similarity index 96% rename from proxy/redir/tproxy_linux.go rename to listener/tproxy/setsockopt_linux.go index 22f8b46b2..1e92db128 100644 --- a/proxy/redir/tproxy_linux.go +++ b/listener/tproxy/setsockopt_linux.go @@ -1,6 +1,7 @@ +//go:build linux // +build linux -package redir +package tproxy import ( "net" diff --git a/proxy/redir/tproxy_other.go b/listener/tproxy/setsockopt_other.go similarity index 83% rename from proxy/redir/tproxy_other.go rename to listener/tproxy/setsockopt_other.go index c20ea7d72..24fb7c7b4 100644 --- a/proxy/redir/tproxy_other.go +++ b/listener/tproxy/setsockopt_other.go @@ -1,6 +1,7 @@ +//go:build !linux // +build !linux -package redir +package tproxy import ( "errors" diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go new file mode 100644 index 000000000..1a09f3663 --- /dev/null +++ b/listener/tproxy/tproxy.go @@ -0,0 +1,75 @@ +package tproxy + +import ( + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" +) + +type Listener struct { + listener net.Listener + addr string + closed bool +} + +// RawAddress implements C.Listener +func (l *Listener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *Listener) Address() string { + return l.listener.Addr().String() +} + +// Close implements C.Listener +func (l *Listener) Close() error { + l.closed = true + return l.listener.Close() +} + +func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) { + target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) + conn.(*net.TCPConn).SetKeepAlive(true) + in <- inbound.NewSocket(target, conn, C.TPROXY) +} + +func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + tl := l.(*net.TCPListener) + rc, err := tl.SyscallConn() + if err != nil { + return nil, err + } + + err = setsockopt(rc, addr) + if err != nil { + return nil, err + } + + rl := &Listener{ + listener: l, + addr: addr, + } + + go func() { + for { + c, err := l.Accept() + if err != nil { + if rl.closed { + break + } + continue + } + go rl.handleTProxy(c, in) + } + }() + + return rl, nil +} diff --git a/listener/tproxy/udp.go b/listener/tproxy/udp.go new file mode 100644 index 000000000..c7e6d99e3 --- /dev/null +++ b/listener/tproxy/udp.go @@ -0,0 +1,91 @@ +package tproxy + +import ( + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/socks5" +) + +type UDPListener struct { + packetConn net.PacketConn + addr string + closed bool +} + +// RawAddress implements C.Listener +func (l *UDPListener) RawAddress() string { + return l.addr +} + +// Address implements C.Listener +func (l *UDPListener) Address() string { + return l.packetConn.LocalAddr().String() +} + +// Close implements C.Listener +func (l *UDPListener) Close() error { + l.closed = true + return l.packetConn.Close() +} + +func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { + l, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + + rl := &UDPListener{ + packetConn: l, + addr: addr, + } + + c := l.(*net.UDPConn) + + rc, err := c.SyscallConn() + if err != nil { + return nil, err + } + + err = setsockopt(rc, addr) + if err != nil { + return nil, err + } + + go func() { + oob := make([]byte, 1024) + for { + buf := pool.Get(pool.UDPBufferSize) + n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) + if err != nil { + pool.Put(buf) + if rl.closed { + break + } + continue + } + + rAddr, err := getOrigDst(oob, oobn) + if err != nil { + continue + } + handlePacketConn(l, in, buf[:n], lAddr, rAddr) + } + }() + + return rl, nil +} + +func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { + target := socks5.ParseAddrToSocksAddr(rAddr) + pkt := &packet{ + lAddr: lAddr, + buf: buf, + } + select { + case in <- inbound.NewPacket(target, pkt, C.TPROXY): + default: + } +} diff --git a/proxy/redir/utils_linux.go b/listener/tproxy/udp_linux.go similarity index 70% rename from proxy/redir/utils_linux.go rename to listener/tproxy/udp_linux.go index 888601a48..f68ce8f1f 100644 --- a/proxy/redir/utils_linux.go +++ b/listener/tproxy/udp_linux.go @@ -1,8 +1,11 @@ +//go:build linux // +build linux -package redir +package tproxy import ( + "encoding/binary" + "errors" "fmt" "net" "os" @@ -10,6 +13,11 @@ import ( "syscall" ) +const ( + IPV6_TRANSPARENT = 0x4b + IPV6_RECVORIGDSTADDR = 0x4a +) + // dialUDP acts like net.DialUDP for transparent proxy. // It binds to a non-local address(`lAddr`). func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { @@ -94,3 +102,24 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { } return syscall.AF_INET6 } + +func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { + msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return nil, err + } + + for _, msg := range msgs { + if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { + ip := net.IP(msg.Data[4:8]) + port := binary.BigEndian.Uint16(msg.Data[2:4]) + return &net.UDPAddr{IP: ip, Port: int(port)}, nil + } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR { + ip := net.IP(msg.Data[8:24]) + port := binary.BigEndian.Uint16(msg.Data[2:4]) + return &net.UDPAddr{IP: ip, Port: int(port)}, nil + } + } + + return nil, errors.New("cannot find origDst") +} diff --git a/proxy/redir/utils_other.go b/listener/tproxy/udp_other.go similarity index 55% rename from proxy/redir/utils_other.go rename to listener/tproxy/udp_other.go index faec71e27..f899fc3d2 100644 --- a/proxy/redir/utils_other.go +++ b/listener/tproxy/udp_other.go @@ -1,12 +1,17 @@ +//go:build !linux // +build !linux -package redir +package tproxy import ( "errors" "net" ) +func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { + return nil, errors.New("UDP redir not supported on current platform") +} + func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { return nil, errors.New("UDP redir not supported on current platform") } diff --git a/log/level.go b/log/level.go index ea223c127..f70116a32 100644 --- a/log/level.go +++ b/log/level.go @@ -5,16 +5,14 @@ import ( "errors" ) -var ( - // LogLevelMapping is a mapping for LogLevel enum - LogLevelMapping = map[string]LogLevel{ - ERROR.String(): ERROR, - WARNING.String(): WARNING, - INFO.String(): INFO, - DEBUG.String(): DEBUG, - SILENT.String(): SILENT, - } -) +// LogLevelMapping is a mapping for LogLevel enum +var LogLevelMapping = map[string]LogLevel{ + ERROR.String(): ERROR, + WARNING.String(): WARNING, + INFO.String(): INFO, + DEBUG.String(): DEBUG, + SILENT.String(): SILENT, +} const ( DEBUG LogLevel = iota diff --git a/main.go b/main.go index 4a7dff0be..5d953ed69 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,8 @@ import ( "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" + + "go.uber.org/automaxprocs/maxprocs" ) var ( @@ -44,6 +46,7 @@ func init() { } func main() { + maxprocs.Set(maxprocs.Logger(func(string, ...interface{}) {})) if version { fmt.Printf("Clash %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime) return diff --git a/proxy/http/server.go b/proxy/http/server.go deleted file mode 100644 index 75f4fc368..000000000 --- a/proxy/http/server.go +++ /dev/null @@ -1,114 +0,0 @@ -package http - -import ( - "bufio" - "encoding/base64" - "net" - "net/http" - "strings" - "time" - - adapters "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/component/auth" - "github.com/Dreamacro/clash/log" - authStore "github.com/Dreamacro/clash/proxy/auth" - "github.com/Dreamacro/clash/tunnel" -) - -type HTTPListener struct { - net.Listener - address string - closed bool - cache *cache.Cache -} - -func NewHTTPProxy(addr string) (*HTTPListener, error) { - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - hl := &HTTPListener{l, addr, false, cache.New(30 * time.Second)} - - go func() { - log.Infoln("HTTP proxy listening at: %s", addr) - - for { - c, err := hl.Accept() - if err != nil { - if hl.closed { - break - } - continue - } - go HandleConn(c, hl.cache) - } - }() - - return hl, nil -} - -func (l *HTTPListener) Close() { - l.closed = true - l.Listener.Close() -} - -func (l *HTTPListener) Address() string { - return l.address -} - -func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) { - if result := cache.Get(loginStr); result != nil { - ret = result.(bool) - return - } - loginData, err := base64.StdEncoding.DecodeString(loginStr) - login := strings.Split(string(loginData), ":") - ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1]) - - cache.Put(loginStr, ret, time.Minute) - return -} - -func HandleConn(conn net.Conn, cache *cache.Cache) { - br := bufio.NewReader(conn) - -keepAlive: - request, err := http.ReadRequest(br) - if err != nil || request.URL.Host == "" { - conn.Close() - return - } - - keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" - authenticator := authStore.Authenticator() - if authenticator != nil { - if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { - conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) - if keepAlive { - goto keepAlive - } - return - } else if !canActivate(authStrings[1], authenticator, cache) { - conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) - log.Infoln("Auth failed from %s", conn.RemoteAddr().String()) - if keepAlive { - goto keepAlive - } - conn.Close() - return - } - } - - if request.Method == http.MethodConnect { - _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) - if err != nil { - conn.Close() - return - } - tunnel.Add(adapters.NewHTTPS(request, conn)) - return - } - - tunnel.Add(adapters.NewHTTP(request, conn)) -} diff --git a/proxy/mixed/mixed.go b/proxy/mixed/mixed.go deleted file mode 100644 index 3328724e1..000000000 --- a/proxy/mixed/mixed.go +++ /dev/null @@ -1,69 +0,0 @@ -package mixed - -import ( - "net" - "time" - - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/component/socks5" - "github.com/Dreamacro/clash/log" - - "github.com/Dreamacro/clash/proxy/http" - "github.com/Dreamacro/clash/proxy/socks" -) - -type MixedListener struct { - net.Listener - address string - closed bool - cache *cache.Cache -} - -func NewMixedProxy(addr string) (*MixedListener, error) { - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)} - go func() { - log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr) - - for { - c, err := ml.Accept() - if err != nil { - if ml.closed { - break - } - continue - } - go handleConn(c, ml.cache) - } - }() - - return ml, nil -} - -func (l *MixedListener) Close() { - l.closed = true - l.Listener.Close() -} - -func (l *MixedListener) Address() string { - return l.address -} - -func handleConn(conn net.Conn, cache *cache.Cache) { - bufConn := NewBufferedConn(conn) - head, err := bufConn.Peek(1) - if err != nil { - return - } - - if head[0] == socks5.Version { - socks.HandleSocks(bufConn) - return - } - - http.HandleConn(bufConn, cache) -} diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go deleted file mode 100644 index e53d000d6..000000000 --- a/proxy/redir/tcp.go +++ /dev/null @@ -1,59 +0,0 @@ -package redir - -import ( - "net" - - "github.com/Dreamacro/clash/adapters/inbound" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" -) - -type RedirListener struct { - net.Listener - address string - closed bool -} - -func NewRedirProxy(addr string) (*RedirListener, error) { - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - rl := &RedirListener{l, addr, false} - - go func() { - log.Infoln("Redir proxy listening at: %s", addr) - for { - c, err := l.Accept() - if err != nil { - if rl.closed { - break - } - continue - } - go handleRedir(c) - } - }() - - return rl, nil -} - -func (l *RedirListener) Close() { - l.closed = true - l.Listener.Close() -} - -func (l *RedirListener) Address() string { - return l.address -} - -func handleRedir(conn net.Conn) { - target, err := parserPacket(conn) - if err != nil { - conn.Close() - return - } - conn.(*net.TCPConn).SetKeepAlive(true) - tunnel.Add(inbound.NewSocket(target, conn, C.REDIR)) -} diff --git a/proxy/redir/tproxy.go b/proxy/redir/tproxy.go deleted file mode 100644 index e2846e4f0..000000000 --- a/proxy/redir/tproxy.go +++ /dev/null @@ -1,71 +0,0 @@ -package redir - -import ( - "net" - - "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/component/socks5" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" -) - -type TProxyListener struct { - net.Listener - address string - closed bool -} - -func NewTProxy(addr string) (*TProxyListener, error) { - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - tl := l.(*net.TCPListener) - rc, err := tl.SyscallConn() - if err != nil { - return nil, err - } - - err = setsockopt(rc, addr) - if err != nil { - return nil, err - } - - rl := &TProxyListener{ - Listener: l, - address: addr, - } - - go func() { - log.Infoln("TProxy server listening at: %s", addr) - for { - c, err := l.Accept() - if err != nil { - if rl.closed { - break - } - continue - } - go rl.handleTProxy(c) - } - }() - - return rl, nil -} - -func (l *TProxyListener) Close() { - l.closed = true - l.Listener.Close() -} - -func (l *TProxyListener) Address() string { - return l.address -} - -func (l *TProxyListener) handleTProxy(conn net.Conn) { - target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) - conn.(*net.TCPConn).SetKeepAlive(true) - tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY)) -} diff --git a/proxy/redir/udp.go b/proxy/redir/udp.go deleted file mode 100644 index 45a726e07..000000000 --- a/proxy/redir/udp.go +++ /dev/null @@ -1,79 +0,0 @@ -package redir - -import ( - "net" - - adapters "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/socks5" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/tunnel" -) - -type RedirUDPListener struct { - net.PacketConn - address string - closed bool -} - -func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { - l, err := net.ListenPacket("udp", addr) - if err != nil { - return nil, err - } - - rl := &RedirUDPListener{l, addr, false} - - c := l.(*net.UDPConn) - - rc, err := c.SyscallConn() - if err != nil { - return nil, err - } - - err = setsockopt(rc, addr) - if err != nil { - return nil, err - } - - go func() { - oob := make([]byte, 1024) - for { - buf := pool.Get(pool.RelayBufferSize) - n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) - if err != nil { - pool.Put(buf) - if rl.closed { - break - } - continue - } - - rAddr, err := getOrigDst(oob, oobn) - if err != nil { - continue - } - handleRedirUDP(l, buf[:n], lAddr, rAddr) - } - }() - - return rl, nil -} - -func (l *RedirUDPListener) Close() error { - l.closed = true - return l.PacketConn.Close() -} - -func (l *RedirUDPListener) Address() string { - return l.address -} - -func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { - target := socks5.ParseAddrToSocksAddr(rAddr) - pkt := &packet{ - lAddr: lAddr, - buf: buf, - } - tunnel.AddPacket(adapters.NewPacket(target, pkt, C.TPROXY)) -} diff --git a/proxy/redir/udp_linux.go b/proxy/redir/udp_linux.go deleted file mode 100644 index e5a53e4da..000000000 --- a/proxy/redir/udp_linux.go +++ /dev/null @@ -1,36 +0,0 @@ -// +build linux - -package redir - -import ( - "encoding/binary" - "errors" - "net" - "syscall" -) - -const ( - IPV6_TRANSPARENT = 0x4b - IPV6_RECVORIGDSTADDR = 0x4a -) - -func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { - msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) - if err != nil { - return nil, err - } - - for _, msg := range msgs { - if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { - ip := net.IP(msg.Data[4:8]) - port := binary.BigEndian.Uint16(msg.Data[2:4]) - return &net.UDPAddr{IP: ip, Port: int(port)}, nil - } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR { - ip := net.IP(msg.Data[8:24]) - port := binary.BigEndian.Uint16(msg.Data[2:4]) - return &net.UDPAddr{IP: ip, Port: int(port)}, nil - } - } - - return nil, errors.New("cannot find origDst") -} diff --git a/proxy/redir/udp_other.go b/proxy/redir/udp_other.go deleted file mode 100644 index e50ec5b97..000000000 --- a/proxy/redir/udp_other.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !linux - -package redir - -import ( - "errors" - "net" -) - -func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { - return nil, errors.New("UDP redir not supported on current platform") -} diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go deleted file mode 100644 index 0f6c4fb4f..000000000 --- a/proxy/socks/tcp.go +++ /dev/null @@ -1,70 +0,0 @@ -package socks - -import ( - "io" - "io/ioutil" - "net" - - adapters "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/component/socks5" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - authStore "github.com/Dreamacro/clash/proxy/auth" - "github.com/Dreamacro/clash/tunnel" -) - -type SockListener struct { - net.Listener - address string - closed bool -} - -func NewSocksProxy(addr string) (*SockListener, error) { - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - sl := &SockListener{l, addr, false} - go func() { - log.Infoln("SOCKS proxy listening at: %s", addr) - for { - c, err := l.Accept() - if err != nil { - if sl.closed { - break - } - continue - } - go HandleSocks(c) - } - }() - - return sl, nil -} - -func (l *SockListener) Close() { - l.closed = true - l.Listener.Close() -} - -func (l *SockListener) Address() string { - return l.address -} - -func HandleSocks(conn net.Conn) { - target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) - if err != nil { - conn.Close() - return - } - if c, ok := conn.(*net.TCPConn); ok { - c.SetKeepAlive(true) - } - if command == socks5.CmdUDPAssociate { - defer conn.Close() - io.Copy(ioutil.Discard, conn) - return - } - tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS)) -} diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go deleted file mode 100644 index 38bc11313..000000000 --- a/proxy/socks/udp.go +++ /dev/null @@ -1,74 +0,0 @@ -package socks - -import ( - "net" - - adapters "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/common/sockopt" - "github.com/Dreamacro/clash/component/socks5" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" -) - -type SockUDPListener struct { - net.PacketConn - address string - closed bool -} - -func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { - l, err := net.ListenPacket("udp", addr) - if err != nil { - return nil, err - } - - err = sockopt.UDPReuseaddr(l.(*net.UDPConn)) - if err != nil { - log.Warnln("Failed to Reuse UDP Address: %s", err) - } - - sl := &SockUDPListener{l, addr, false} - go func() { - for { - buf := pool.Get(pool.RelayBufferSize) - n, remoteAddr, err := l.ReadFrom(buf) - if err != nil { - pool.Put(buf) - if sl.closed { - break - } - continue - } - handleSocksUDP(l, buf[:n], remoteAddr) - } - }() - - return sl, nil -} - -func (l *SockUDPListener) Close() error { - l.closed = true - return l.PacketConn.Close() -} - -func (l *SockUDPListener) Address() string { - return l.address -} - -func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { - target, payload, err := socks5.DecodeUDPPacket(buf) - if err != nil { - // Unresolved UDP packet, return buffer to the pool - pool.Put(buf) - return - } - packet := &packet{ - pc: pc, - rAddr: addr, - payload: payload, - bufRef: buf, - } - tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) -} diff --git a/rules/base.go b/rule/base.go similarity index 100% rename from rules/base.go rename to rule/base.go diff --git a/rules/domain.go b/rule/domain.go similarity index 100% rename from rules/domain.go rename to rule/domain.go diff --git a/rules/domain_keyword.go b/rule/domain_keyword.go similarity index 100% rename from rules/domain_keyword.go rename to rule/domain_keyword.go diff --git a/rules/domain_suffix.go b/rule/domain_suffix.go similarity index 100% rename from rules/domain_suffix.go rename to rule/domain_suffix.go diff --git a/rules/final.go b/rule/final.go similarity index 100% rename from rules/final.go rename to rule/final.go diff --git a/rules/geoip.go b/rule/geoip.go similarity index 84% rename from rules/geoip.go rename to rule/geoip.go index be4b5029f..3b6fbe6b8 100644 --- a/rules/geoip.go +++ b/rule/geoip.go @@ -1,6 +1,8 @@ package rules import ( + "strings" + "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" ) @@ -20,8 +22,12 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool { if ip == nil { return false } + + if strings.EqualFold(g.country, "LAN") { + return ip.IsPrivate() + } record, _ := mmdb.Instance().Country(ip) - return record.Country.IsoCode == g.country + return strings.EqualFold(record.Country.IsoCode, g.country) } func (g *GEOIP) Adapter() string { diff --git a/rules/ipcidr.go b/rule/ipcidr.go similarity index 100% rename from rules/ipcidr.go rename to rule/ipcidr.go diff --git a/rules/parser.go b/rule/parser.go similarity index 100% rename from rules/parser.go rename to rule/parser.go diff --git a/rules/port.go b/rule/port.go similarity index 94% rename from rules/port.go rename to rule/port.go index 281a4c4df..b6e8abb1c 100644 --- a/rules/port.go +++ b/rule/port.go @@ -39,7 +39,7 @@ func (p *Port) ShouldResolveIP() bool { } func NewPort(port string, adapter string, isSource bool) (*Port, error) { - _, err := strconv.Atoi(port) + _, err := strconv.ParseUint(port, 10, 16) if err != nil { return nil, errPayload } diff --git a/rules/process.go b/rule/process.go similarity index 100% rename from rules/process.go rename to rule/process.go diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 000000000..012d88d52 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,8 @@ +lint: + golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... + +test: + go test -p 1 -v ./... + +benchmark: + go test -benchmem -run=^$ -bench . diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..a95f3aea6 --- /dev/null +++ b/test/README.md @@ -0,0 +1,59 @@ +## Clash testing suit + +### Protocol testing suit + +* TCP pingpong test +* UDP pingpong test +* TCP large data test +* UDP large data test + +### Protocols + +- [x] Shadowsocks + - [x] Normal + - [x] ObfsHTTP + - [x] ObfsTLS + - [x] ObfsV2rayPlugin +- [x] Vmess + - [x] Normal + - [x] AEAD + - [x] HTTP + - [x] HTTP2 + - [x] TLS + - [x] Websocket + - [x] Websocket TLS + - [x] gRPC +- [x] Trojan + - [x] Normal + - [x] gRPC +- [x] Snell + - [x] Normal + - [x] ObfsHTTP + - [x] ObfsTLS + +### Features + +- [ ] DNS + - [x] DNS Server + - [x] FakeIP + - [x] Host + +### Command + +Prerequisite + +* docker (support Linux and macOS) + +``` +$ make test +``` + +benchmark (Linux) + +> Cannot represent the throughput of the protocol on your machine +> but you can compare the corresponding throughput of the protocol on clash +> (change chunkSize to measure the maximum throughput of clash on your machine) + +``` +$ make benchmark +``` diff --git a/test/clash_test.go b/test/clash_test.go new file mode 100644 index 000000000..5eb9d5bda --- /dev/null +++ b/test/clash_test.go @@ -0,0 +1,684 @@ +package main + +import ( + "context" + "crypto/md5" + "crypto/rand" + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "runtime" + "sync" + "testing" + "time" + + "github.com/Dreamacro/clash/adapter/outbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/hub/executor" + "github.com/Dreamacro/clash/transport/socks5" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" +) + +const ( + ImageShadowsocks = "mritd/shadowsocks:latest" + ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest" + ImageVmess = "v2fly/v2fly-core:latest" + ImageTrojan = "trojangfw/trojan:latest" + ImageTrojanGo = "p4gefau1t/trojan-go:latest" + ImageSnell = "icpz/snell-server:latest" + ImageXray = "teddysun/xray:latest" +) + +var ( + waitTime = time.Second + localIP = net.ParseIP("127.0.0.1") + + defaultExposedPorts = nat.PortSet{ + "10002/tcp": struct{}{}, + "10002/udp": struct{}{}, + } + defaultPortBindings = nat.PortMap{ + "10002/tcp": []nat.PortBinding{ + {HostPort: "10002", HostIP: "0.0.0.0"}, + }, + "10002/udp": []nat.PortBinding{ + {HostPort: "10002", HostIP: "0.0.0.0"}, + }, + } +) + +func init() { + if runtime.GOOS == "darwin" { + isDarwin = true + } + + currentDir, err := os.Getwd() + if err != nil { + panic(err) + } + homeDir := filepath.Join(currentDir, "config") + C.SetHomeDir(homeDir) + + if isDarwin { + localIP, err = defaultRouteIP() + if err != nil { + panic(err) + } + } + + c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + panic(err) + } + defer c.Close() + + list, err := c.ImageList(context.Background(), types.ImageListOptions{All: true}) + if err != nil { + panic(err) + } + + imageExist := func(image string) bool { + for _, item := range list { + for _, tag := range item.RepoTags { + if image == tag { + return true + } + } + } + return false + } + + images := []string{ + ImageShadowsocks, + ImageShadowsocksRust, + ImageVmess, + ImageTrojan, + ImageTrojanGo, + ImageSnell, + ImageXray, + } + + for _, image := range images { + if imageExist(image) { + continue + } + + imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{}) + if err != nil { + panic(err) + } + + io.Copy(io.Discard, imageStream) + } +} + +var clean = ` +port: 0 +socks-port: 0 +mixed-port: 0 +redir-port: 0 +tproxy-port: 0 +dns: + enable: false +` + +func cleanup() { + parseAndApply(clean) +} + +func parseAndApply(cfgStr string) error { + cfg, err := executor.ParseWithBytes([]byte(cfgStr)) + if err != nil { + return err + } + + executor.ApplyConfig(cfg, true) + return nil +} + +func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) { + pingCh := make(chan []byte) + pongCh := make(chan []byte) + test := func(t *testing.T) error { + defer close(pingCh) + defer close(pongCh) + pingOpen := false + pongOpen := false + var recv []byte + + for { + if pingOpen && pongOpen { + break + } + + select { + case recv, pingOpen = <-pingCh: + assert.True(t, pingOpen) + assert.Equal(t, []byte("ping"), recv) + case recv, pongOpen = <-pongCh: + assert.True(t, pongOpen) + assert.Equal(t, []byte("pong"), recv) + case <-time.After(10 * time.Second): + return errors.New("timeout") + } + } + return nil + } + + return pingCh, pongCh, test +} + +func newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error) { + pingCh := make(chan hashPair) + pongCh := make(chan hashPair) + test := func(t *testing.T) error { + defer close(pingCh) + defer close(pongCh) + pingOpen := false + pongOpen := false + var serverPair hashPair + var clientPair hashPair + + for { + if pingOpen && pongOpen { + break + } + + select { + case serverPair, pingOpen = <-pingCh: + assert.True(t, pingOpen) + case clientPair, pongOpen = <-pongCh: + assert.True(t, pongOpen) + case <-time.After(10 * time.Second): + return errors.New("timeout") + } + } + + assert.Equal(t, serverPair.recvHash, clientPair.sendHash) + assert.Equal(t, serverPair.sendHash, clientPair.recvHash) + + return nil + } + + return pingCh, pongCh, test +} + +func testPingPongWithSocksPort(t *testing.T, port int) { + pingCh, pongCh, test := newPingPongPair() + go func() { + l, err := Listen("tcp", ":10001") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer l.Close() + + c, err := l.Accept() + if err != nil { + assert.FailNow(t, err.Error()) + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + assert.FailNow(t, err.Error()) + } + + pingCh <- buf + if _, err := c.Write([]byte("pong")); err != nil { + assert.FailNow(t, err.Error()) + } + }() + + go func() { + c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer c.Close() + + if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil { + assert.FailNow(t, err.Error()) + } + + if _, err := c.Write([]byte("ping")); err != nil { + assert.FailNow(t, err.Error()) + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + assert.FailNow(t, err.Error()) + } + + pongCh <- buf + }() + + test(t) +} + +func testPingPongWithConn(t *testing.T, c net.Conn) error { + l, err := Listen("tcp", ":10001") + if err != nil { + return err + } + defer l.Close() + + pingCh, pongCh, test := newPingPongPair() + go func() { + c, err := l.Accept() + if err != nil { + return + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + return + } + + pingCh <- buf + if _, err := c.Write([]byte("pong")); err != nil { + return + } + }() + + go func() { + if _, err := c.Write([]byte("ping")); err != nil { + return + } + + buf := make([]byte, 4) + if _, err := io.ReadFull(c, buf); err != nil { + return + } + + pongCh <- buf + }() + + return test(t) +} + +func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error { + l, err := ListenPacket("udp", ":10001") + if err != nil { + return err + } + defer l.Close() + + rAddr := &net.UDPAddr{IP: localIP, Port: 10001} + + pingCh, pongCh, test := newPingPongPair() + go func() { + buf := make([]byte, 1024) + n, rAddr, err := l.ReadFrom(buf) + if err != nil { + return + } + + pingCh <- buf[:n] + if _, err := l.WriteTo([]byte("pong"), rAddr); err != nil { + return + } + }() + + go func() { + if _, err := pc.WriteTo([]byte("ping"), rAddr); err != nil { + return + } + + buf := make([]byte, 1024) + n, _, err := pc.ReadFrom(buf) + if err != nil { + return + } + + pongCh <- buf[:n] + }() + + return test(t) +} + +type hashPair struct { + sendHash map[int][]byte + recvHash map[int][]byte +} + +func testLargeDataWithConn(t *testing.T, c net.Conn) error { + l, err := Listen("tcp", ":10001") + if err != nil { + return err + } + defer l.Close() + + times := 100 + chunkSize := int64(64 * 1024) + + pingCh, pongCh, test := newLargeDataPair() + writeRandData := func(conn net.Conn) (map[int][]byte, error) { + buf := make([]byte, chunkSize) + hashMap := map[int][]byte{} + for i := 0; i < times; i++ { + if _, err := rand.Read(buf[1:]); err != nil { + return nil, err + } + buf[0] = byte(i) + + hash := md5.Sum(buf) + hashMap[i] = hash[:] + + if _, err := conn.Write(buf); err != nil { + return nil, err + } + } + + return hashMap, nil + } + + go func() { + c, err := l.Accept() + if err != nil { + return + } + defer c.Close() + + hashMap := map[int][]byte{} + buf := make([]byte, chunkSize) + + for i := 0; i < times; i++ { + _, err := io.ReadFull(c, buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf) + hashMap[int(buf[0])] = hash[:] + } + + sendHash, err := writeRandData(c) + if err != nil { + t.Log(err.Error()) + return + } + + pingCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + go func() { + sendHash, err := writeRandData(c) + if err != nil { + t.Log(err.Error()) + return + } + + hashMap := map[int][]byte{} + buf := make([]byte, chunkSize) + + for i := 0; i < times; i++ { + _, err := io.ReadFull(c, buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf) + hashMap[int(buf[0])] = hash[:] + } + + pongCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + return test(t) +} + +func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error { + l, err := ListenPacket("udp", ":10001") + if err != nil { + return err + } + defer l.Close() + + rAddr := &net.UDPAddr{IP: localIP, Port: 10001} + + times := 50 + chunkSize := int64(1024) + + pingCh, pongCh, test := newLargeDataPair() + writeRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) { + hashMap := map[int][]byte{} + mux := sync.Mutex{} + for i := 0; i < times; i++ { + go func(idx int) { + buf := make([]byte, chunkSize) + if _, err := rand.Read(buf[1:]); err != nil { + t.Log(err.Error()) + return + } + buf[0] = byte(idx) + + hash := md5.Sum(buf) + mux.Lock() + hashMap[idx] = hash[:] + mux.Unlock() + + if _, err := pc.WriteTo(buf, addr); err != nil { + t.Log(err.Error()) + return + } + }(i) + } + + return hashMap, nil + } + + go func() { + var rAddr net.Addr + hashMap := map[int][]byte{} + buf := make([]byte, 64*1024) + + for i := 0; i < times; i++ { + _, rAddr, err = l.ReadFrom(buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf[:chunkSize]) + hashMap[int(buf[0])] = hash[:] + } + + sendHash, err := writeRandData(l, rAddr) + if err != nil { + t.Log(err.Error()) + return + } + + pingCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + go func() { + sendHash, err := writeRandData(pc, rAddr) + if err != nil { + t.Log(err.Error()) + return + } + + hashMap := map[int][]byte{} + buf := make([]byte, 64*1024) + + for i := 0; i < times; i++ { + _, _, err := pc.ReadFrom(buf) + if err != nil { + t.Log(err.Error()) + return + } + + hash := md5.Sum(buf[:chunkSize]) + hashMap[int(buf[0])] = hash[:] + } + + pongCh <- hashPair{ + sendHash: sendHash, + recvHash: hashMap, + } + }() + + return test(t) +} + +func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error { + err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300)) + assert.NoError(t, err) + + errCh := make(chan error, 1) + go func() { + buf := make([]byte, 1024) + _, _, err := pc.ReadFrom(buf) + errCh <- err + }() + + select { + case <-errCh: + return nil + case <-time.After(time.Second * 10): + return errors.New("timeout") + } +} + +func testSuit(t *testing.T, proxy C.ProxyAdapter) { + conn, err := proxy.DialContext(context.Background(), &C.Metadata{ + Host: localIP.String(), + DstPort: "10001", + AddrType: socks5.AtypDomainName, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer conn.Close() + assert.NoError(t, testPingPongWithConn(t, conn)) + + conn, err = proxy.DialContext(context.Background(), &C.Metadata{ + Host: localIP.String(), + DstPort: "10001", + AddrType: socks5.AtypDomainName, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer conn.Close() + assert.NoError(t, testLargeDataWithConn(t, conn)) + + if !proxy.SupportUDP() { + return + } + + pc, err := proxy.ListenPacketContext(context.Background(), &C.Metadata{ + NetWork: C.UDP, + DstIP: localIP, + DstPort: "10001", + AddrType: socks5.AtypIPv4, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer pc.Close() + + assert.NoError(t, testPingPongWithPacketConn(t, pc)) + + pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{ + NetWork: C.UDP, + DstIP: localIP, + DstPort: "10001", + AddrType: socks5.AtypIPv4, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer pc.Close() + + assert.NoError(t, testLargeDataWithPacketConn(t, pc)) + + pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{ + NetWork: C.UDP, + DstIP: localIP, + DstPort: "10001", + AddrType: socks5.AtypIPv4, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + defer pc.Close() + + assert.NoError(t, testPacketConnTimeout(t, pc)) +} + +func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) { + l, err := Listen("tcp", ":10001") + if err != nil { + assert.FailNow(b, err.Error()) + } + defer l.Close() + + go func() { + c, err := l.Accept() + if err != nil { + assert.FailNow(b, err.Error()) + } + defer c.Close() + + io.Copy(io.Discard, c) + }() + + chunkSize := int64(16 * 1024) + chunk := make([]byte, chunkSize) + rand.Read(chunk) + conn, err := proxy.DialContext(context.Background(), &C.Metadata{ + Host: localIP.String(), + DstPort: "10001", + AddrType: socks5.AtypDomainName, + }) + if err != nil { + assert.FailNow(b, err.Error()) + } + + b.SetBytes(chunkSize) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.Write(chunk); err != nil { + assert.FailNow(b, err.Error()) + } + } +} + +func TestClash_Basic(t *testing.T) { + basic := ` +mixed-port: 10000 +log-level: silent +` + + if err := parseAndApply(basic); err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanup() + + time.Sleep(waitTime) + testPingPongWithSocksPort(t, 10000) +} + +func Benchmark_Direct(b *testing.B) { + proxy := outbound.NewDirect() + benchmarkProxy(b, proxy) +} diff --git a/test/config/example.org-key.pem b/test/config/example.org-key.pem new file mode 100644 index 000000000..dbe9a3db3 --- /dev/null +++ b/test/config/example.org-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5 +5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo +PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE +sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R +i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5 +LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge +gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+ +y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO +jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z +ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv +H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG +o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ +CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49 +aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33 +bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7 +Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh +ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO +pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT +4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi +GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA +vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB +fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z +zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X +DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28 +9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP +XCar+uxMBXI1zbXqd9QdEwy4Ig== +-----END PRIVATE KEY----- diff --git a/test/config/example.org.pem b/test/config/example.org.pem new file mode 100644 index 000000000..9b99259a3 --- /dev/null +++ b/test/config/example.org.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB +hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh +bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl +cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz +MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl +bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy +by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8 +3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI +YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b +IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo +UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2 +I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln +F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP +TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa +Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1 +Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz +Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG +WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6 +AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6 +gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS +tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w= +-----END CERTIFICATE----- diff --git a/test/config/snell-http.conf b/test/config/snell-http.conf new file mode 100644 index 000000000..f5130dc10 --- /dev/null +++ b/test/config/snell-http.conf @@ -0,0 +1,4 @@ +[snell-server] +listen = 0.0.0.0:10002 +psk = password +obfs = http diff --git a/test/config/snell-tls.conf b/test/config/snell-tls.conf new file mode 100644 index 000000000..bf8b51396 --- /dev/null +++ b/test/config/snell-tls.conf @@ -0,0 +1,4 @@ +[snell-server] +listen = 0.0.0.0:10002 +psk = password +obfs = tls diff --git a/test/config/snell.conf b/test/config/snell.conf new file mode 100644 index 000000000..40266391b --- /dev/null +++ b/test/config/snell.conf @@ -0,0 +1,3 @@ +[snell-server] +listen = 0.0.0.0:10002 +psk = password diff --git a/test/config/trojan-grpc.json b/test/config/trojan-grpc.json new file mode 100644 index 000000000..eb0dcc990 --- /dev/null +++ b/test/config/trojan-grpc.json @@ -0,0 +1,40 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "trojan", + "settings": { + "clients": [ + { + "password": "example", + "email": "grpc@example.com" + } + ] + }, + "streamSettings": { + "network": "grpc", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "grpcSettings": { + "serviceName": "example" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/trojan-ws.json b/test/config/trojan-ws.json new file mode 100644 index 000000000..efc0acbd0 --- /dev/null +++ b/test/config/trojan-ws.json @@ -0,0 +1,20 @@ +{ + "run_type": "server", + "local_addr": "0.0.0.0", + "local_port": 10002, + "disable_http_check": true, + "password": [ + "example" + ], + "websocket": { + "enabled": true, + "path": "/", + "host": "example.org" + }, + "ssl": { + "verify": true, + "cert": "/fullchain.pem", + "key": "/privkey.pem", + "sni": "example.org" + } +} \ No newline at end of file diff --git a/test/config/trojan.json b/test/config/trojan.json new file mode 100644 index 000000000..18e33a980 --- /dev/null +++ b/test/config/trojan.json @@ -0,0 +1,40 @@ +{ + "run_type": "server", + "local_addr": "0.0.0.0", + "local_port": 10002, + "password": [ + "password" + ], + "log_level": 1, + "ssl": { + "cert": "/path/to/certificate.crt", + "key": "/path/to/private.key", + "key_password": "", + "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384", + "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", + "prefer_server_cipher": true, + "alpn": [ + "http/1.1" + ], + "alpn_port_override": { + "h2": 81 + }, + "reuse_session": true, + "session_ticket": false, + "session_timeout": 600, + "plain_http_response": "", + "curves": "", + "dhparam": "" + }, + "tcp": { + "prefer_ipv4": false, + "no_delay": true, + "keep_alive": true, + "reuse_port": false, + "fast_open": false, + "fast_open_qlen": 20 + }, + "mysql": { + "enabled": false + } +} \ No newline at end of file diff --git a/test/config/vmess-aead.json b/test/config/vmess-aead.json new file mode 100644 index 000000000..27bc4757f --- /dev/null +++ b/test/config/vmess-aead.json @@ -0,0 +1,28 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 0 + } + ] + }, + "streamSettings": { + "network": "tcp" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-grpc.json b/test/config/vmess-grpc.json new file mode 100644 index 000000000..22e117634 --- /dev/null +++ b/test/config/vmess-grpc.json @@ -0,0 +1,40 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "grpc", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "grpcSettings": { + "serviceName": "example!" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-http.json b/test/config/vmess-http.json new file mode 100644 index 000000000..aa6c6c8ed --- /dev/null +++ b/test/config/vmess-http.json @@ -0,0 +1,55 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "tcp", + "tcpSettings": { + "header": { + "type": "http", + "response": { + "version": "1.1", + "status": "200", + "reason": "OK", + "headers": { + "Content-Type": [ + "application/octet-stream", + "video/mpeg", + "application/x-msdownload", + "text/html", + "application/x-shockwave-flash" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Connection": [ + "keep-alive" + ], + "Pragma": "no-cache" + } + } + } + }, + "security": "none" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-http2.json b/test/config/vmess-http2.json new file mode 100644 index 000000000..488737311 --- /dev/null +++ b/test/config/vmess-http2.json @@ -0,0 +1,43 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "http", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + }, + "httpSettings": { + "host": [ + "example.org" + ], + "path": "/test" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-tls.json b/test/config/vmess-tls.json new file mode 100644 index 000000000..ae2fa489b --- /dev/null +++ b/test/config/vmess-tls.json @@ -0,0 +1,37 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/config/vmess-ws-0rtt.json b/test/config/vmess-ws-0rtt.json new file mode 100644 index 000000000..7e2876d0c --- /dev/null +++ b/test/config/vmess-ws-0rtt.json @@ -0,0 +1,30 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "none", + "wsSettings": { + "maxEarlyData": 128, + "earlyDataHeaderName": "Sec-WebSocket-Protocol" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} \ No newline at end of file diff --git a/test/config/vmess-ws-tls.json b/test/config/vmess-ws-tls.json new file mode 100644 index 000000000..dda1e0c96 --- /dev/null +++ b/test/config/vmess-ws-tls.json @@ -0,0 +1,34 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/v2ray/fullchain.pem", + "keyFile": "/etc/ssl/v2ray/privkey.pem" + } + ] + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} \ No newline at end of file diff --git a/test/config/vmess-ws.json b/test/config/vmess-ws.json new file mode 100644 index 000000000..94ace4e32 --- /dev/null +++ b/test/config/vmess-ws.json @@ -0,0 +1,26 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "none" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} \ No newline at end of file diff --git a/test/config/vmess.json b/test/config/vmess.json new file mode 100644 index 000000000..bcf53cd8c --- /dev/null +++ b/test/config/vmess.json @@ -0,0 +1,28 @@ +{ + "inbounds": [ + { + "port": 10002, + "listen": "0.0.0.0", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", + "alterId": 32 + } + ] + }, + "streamSettings": { + "network": "tcp" + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ], + "log": { + "loglevel": "debug" + } +} \ No newline at end of file diff --git a/test/dns_test.go b/test/dns_test.go new file mode 100644 index 000000000..dd07bda84 --- /dev/null +++ b/test/dns_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func exchange(address, domain string, tp uint16) ([]dns.RR, error) { + client := dns.Client{} + query := &dns.Msg{} + query.SetQuestion(dns.Fqdn(domain), tp) + + r, _, err := client.Exchange(query, address) + if err != nil { + return nil, err + } + return r.Answer, nil +} + +func TestClash_DNS(t *testing.T) { + basic := ` +log-level: silent +dns: + enable: true + listen: 0.0.0.0:8553 + nameserver: + - 119.29.29.29 +` + + if err := parseAndApply(basic); err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanup() + + time.Sleep(waitTime) + + rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA) + assert.NoError(t, err) + if !assert.NotEmpty(t, rr) { + assert.FailNow(t, "record empty") + } + + record := rr[0].(*dns.A) + assert.Equal(t, record.A.String(), "1.1.1.1") + + rr, err = exchange("127.0.0.1:8553", "2606-4700-4700--1111.sslip.io", dns.TypeAAAA) + assert.NoError(t, err) + assert.Empty(t, rr) +} + +func TestClash_DNSHostAndFakeIP(t *testing.T) { + basic := ` +log-level: silent +hosts: + foo.clash.dev: 1.1.1.1 +dns: + enable: true + listen: 0.0.0.0:8553 + ipv6: true + enhanced-mode: fake-ip + fake-ip-range: 198.18.0.1/16 + fake-ip-filter: + - .sslip.io + nameserver: + - 119.29.29.29 +` + + if err := parseAndApply(basic); err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanup() + + time.Sleep(waitTime) + + type domainPair struct { + domain string + ip string + } + + list := []domainPair{ + {"foo.org", "198.18.0.2"}, + {"bar.org", "198.18.0.3"}, + {"foo.org", "198.18.0.2"}, + {"foo.clash.dev", "1.1.1.1"}, + } + + for _, pair := range list { + rr, err := exchange("127.0.0.1:8553", pair.domain, dns.TypeA) + assert.NoError(t, err) + assert.NotEmpty(t, rr) + + record := rr[0].(*dns.A) + assert.Equal(t, record.A.String(), pair.ip) + } + + rr, err := exchange("127.0.0.1:8553", "2606-4700-4700--1111.sslip.io", dns.TypeAAAA) + assert.NoError(t, err) + assert.NotEmpty(t, rr) + assert.Equal(t, rr[0].(*dns.AAAA).AAAA.String(), "2606:4700:4700::1111") +} diff --git a/test/docker_test.go b/test/docker_test.go new file mode 100644 index 000000000..12c427b18 --- /dev/null +++ b/test/docker_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" +) + +var isDarwin = false + +func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) { + c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return "", err + } + defer c.Close() + + if !isDarwin { + hostCfg.NetworkMode = "host" + } + + container, err := c.ContainerCreate(context.Background(), cfg, hostCfg, nil, nil, name) + if err != nil { + return "", err + } + + if err = c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil { + return "", err + } + + return container.ID, nil +} + +func cleanContainer(id string) error { + c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return err + } + defer c.Close() + + removeOpts := types.ContainerRemoveOptions{Force: true} + return c.ContainerRemove(context.Background(), id, removeOpts) +} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 000000000..a43e4f5c9 --- /dev/null +++ b/test/go.mod @@ -0,0 +1,51 @@ +module clash-test + +go 1.17 + +require ( + github.com/Dreamacro/clash v1.7.2-0.20211108085948-bd2ea2b917aa + github.com/docker/docker v20.10.10+incompatible + github.com/docker/go-connections v0.4.0 + github.com/miekg/dns v1.1.43 + github.com/stretchr/testify v1.7.0 + golang.org/x/net v0.0.0-20211105192438-b53810dc28af +) + +replace github.com/Dreamacro/clash => ../ + +require ( + github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect + github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/containerd/containerd v1.5.7 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/gofrs/uuid v4.1.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/insomniacslk/dhcp v0.0.0-20211026125128-ad197bcd36fd // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/oschwald/geoip2-golang v1.5.0 // indirect + github.com/oschwald/maxminddb-golang v1.8.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect + go.etcd.io/bbolt v1.3.6 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect + google.golang.org/grpc v1.42.0 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/test/go.sum b/test/go.sum new file mode 100644 index 000000000..061bb3a4d --- /dev/null +++ b/test/go.sum @@ -0,0 +1,1033 @@ +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/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/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/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= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +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.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +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/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +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.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/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/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +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-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +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/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +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/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.10+incompatible h1:GKkP0T7U4ks6X3lmmHKC2QDprnpRJor2Z5a8m62R9ZM= +github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/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.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi/v5 v5.0.5/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +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-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk= +github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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-20190129154638-5b532d6fd5ef/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/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 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +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/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.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/google/uuid v1.2.0/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.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/insomniacslk/dhcp v0.0.0-20211026125128-ad197bcd36fd h1:jupbuQFZtwOBg/3EmK91/rGaYFkqCb9bwHOnwn7Cav0= +github.com/insomniacslk/dhcp v0.0.0-20211026125128-ad197bcd36fd/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +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-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +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.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +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/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +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/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +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/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/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-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= +github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +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 v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +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.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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/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/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-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190419010253-1f3472d942ba/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20191007182048-72f939374954/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-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-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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-20211105192438-b53810dc28af h1:SMeNJG/vclJ5wyBBd4xupMsSJIHTd1coW9g7q6KOjmY= +golang.org/x/net v0.0.0-20211105192438-b53810dc28af/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/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-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-20201207232520-09787c993a3a/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-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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/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-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/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-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/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-20191115151921-52ab43148777/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-20200106162015-b016eb3dc98e/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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/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-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 h1:G2DDmludOQZoWbpCr7OKDxnl478ZBGMcOhrv+ooX/Q4= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/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-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-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190328211700-ab21143f2384/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-20190614205625-5aca471b1d59/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-20190624222133-a101b041ded4/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-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +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.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +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-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +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-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +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.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +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.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +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.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/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +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/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/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 h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +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= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +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/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/test/snell_test.go b/test/snell_test.go new file mode 100644 index 000000000..f9cd610c0 --- /dev/null +++ b/test/snell_test.go @@ -0,0 +1,158 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/Dreamacro/clash/adapter/outbound" + C "github.com/Dreamacro/clash/constant" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_SnellObfsHTTP(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell-http") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + ObfsOpts: map[string]interface{}{ + "mode": "http", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_SnellObfsTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-tls.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell-tls") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + ObfsOpts: map[string]interface{}{ + "mode": "tls", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_Snell(t *testing.T) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func Benchmark_Snell(b *testing.B) { + cfg := &container.Config{ + Image: ImageSnell, + ExposedPorts: defaultExposedPorts, + Cmd: []string{"-c", "/config.conf"}, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))}, + } + + id, err := startContainer(cfg, hostCfg, "snell-http") + if err != nil { + assert.FailNow(b, err.Error()) + } + + b.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewSnell(outbound.SnellOption{ + Name: "snell", + Server: localIP.String(), + Port: 10002, + Psk: "password", + ObfsOpts: map[string]interface{}{ + "mode": "http", + }, + }) + if err != nil { + assert.FailNow(b, err.Error()) + } + + time.Sleep(waitTime) + benchmarkProxy(b, proxy) +} diff --git a/test/ss_test.go b/test/ss_test.go new file mode 100644 index 000000000..446bae938 --- /dev/null +++ b/test/ss_test.go @@ -0,0 +1,209 @@ +package main + +import ( + "testing" + "time" + + "github.com/Dreamacro/clash/adapter/outbound" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_Shadowsocks(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocksRust, + Entrypoint: []string{"ssserver"}, + Cmd: []string{"-s", "0.0.0.0:10002", "-m", "chacha20-ietf-poly1305", "-k", "FzcLbKs2dY9mhL", "-U"}, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_ShadowsocksObfsHTTP(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{ + "SS_MODULE=ss-server", + "SS_CONFIG=-s 0.0.0.0 -u -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL --plugin obfs-server --plugin-opts obfs=http", + }, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss-obfs-http") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + Plugin: "obfs", + PluginOpts: map[string]interface{}{ + "mode": "http", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_ShadowsocksObfsTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{ + "SS_MODULE=ss-server", + "SS_CONFIG=-s 0.0.0.0 -u -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL --plugin obfs-server --plugin-opts obfs=tls", + }, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss-obfs-tls") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + Plugin: "obfs", + PluginOpts: map[string]interface{}{ + "mode": "tls", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_ShadowsocksV2RayPlugin(t *testing.T) { + cfg := &container.Config{ + Image: ImageShadowsocks, + Env: []string{ + "SS_MODULE=ss-server", + "SS_CONFIG=-s 0.0.0.0 -u -p 10002 -m chacha20-ietf-poly1305 -k FzcLbKs2dY9mhL --plugin v2ray-plugin --plugin-opts=server", + }, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "chacha20-ietf-poly1305", + UDP: true, + Plugin: "v2ray-plugin", + PluginOpts: map[string]interface{}{ + "mode": "websocket", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func Benchmark_Shadowsocks(b *testing.B) { + cfg := &container.Config{ + Image: ImageShadowsocksRust, + Entrypoint: []string{"ssserver"}, + Cmd: []string{"-s", "0.0.0.0:10002", "-m", "aes-256-gcm", "-k", "FzcLbKs2dY9mhL", "-U"}, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + } + + id, err := startContainer(cfg, hostCfg, "ss") + if err != nil { + assert.FailNow(b, err.Error()) + } + + b.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{ + Name: "ss", + Server: localIP.String(), + Port: 10002, + Password: "FzcLbKs2dY9mhL", + Cipher: "aes-256-gcm", + UDP: true, + }) + if err != nil { + assert.FailNow(b, err.Error()) + } + + time.Sleep(waitTime) + benchmarkProxy(b, proxy) +} diff --git a/test/trojan_test.go b/test/trojan_test.go new file mode 100644 index 000000000..d1ab2a006 --- /dev/null +++ b/test/trojan_test.go @@ -0,0 +1,172 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/Dreamacro/clash/adapter/outbound" + C "github.com/Dreamacro/clash/constant" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_Trojan(t *testing.T) { + cfg := &container.Config{ + Image: ImageTrojan, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/config/config.json", C.Path.Resolve("trojan.json")), + fmt.Sprintf("%s:/path/to/certificate.crt", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/path/to/private.key", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "password", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_TrojanGrpc(t *testing.T) { + cfg := &container.Config{ + Image: ImageXray, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("trojan-grpc.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan-grpc") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "example", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{ + GrpcServiceName: "example", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_TrojanWebsocket(t *testing.T) { + cfg := &container.Config{ + Image: ImageTrojanGo, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/trojan-go/config.json", C.Path.Resolve("trojan-ws.json")), + fmt.Sprintf("%s:/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan-ws") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "example", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + Network: "ws", + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func Benchmark_Trojan(b *testing.B) { + cfg := &container.Config{ + Image: ImageTrojan, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/config/config.json", C.Path.Resolve("trojan.json")), + fmt.Sprintf("%s:/path/to/certificate.crt", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/path/to/private.key", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "trojan") + if err != nil { + assert.FailNow(b, err.Error()) + } + + b.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewTrojan(outbound.TrojanOption{ + Name: "trojan", + Server: localIP.String(), + Port: 10002, + Password: "password", + SNI: "example.org", + SkipCertVerify: true, + UDP: true, + }) + if err != nil { + assert.FailNow(b, err.Error()) + } + + time.Sleep(waitTime) + benchmarkProxy(b, proxy) +} diff --git a/test/util.go b/test/util.go new file mode 100644 index 000000000..f3325aeb6 --- /dev/null +++ b/test/util.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "net" + "time" +) + +func Listen(network, address string) (net.Listener, error) { + lc := net.ListenConfig{} + + var lastErr error + for i := 0; i < 5; i++ { + l, err := lc.Listen(context.Background(), network, address) + if err == nil { + return l, nil + } + + lastErr = err + time.Sleep(time.Millisecond * 200) + } + return nil, lastErr +} + +func ListenPacket(network, address string) (net.PacketConn, error) { + var lastErr error + for i := 0; i < 5; i++ { + l, err := net.ListenPacket(network, address) + if err == nil { + return l, nil + } + + lastErr = err + time.Sleep(time.Millisecond * 200) + } + return nil, lastErr +} diff --git a/test/util_darwin_test.go b/test/util_darwin_test.go new file mode 100644 index 000000000..ab9b8b2bf --- /dev/null +++ b/test/util_darwin_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "errors" + "fmt" + "net" + "syscall" + + "golang.org/x/net/route" +) + +func defaultRouteIP() (net.IP, error) { + idx, err := defaultRouteInterfaceIndex() + if err != nil { + return nil, err + } + iface, err := net.InterfaceByIndex(idx) + if err != nil { + return nil, err + } + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + for _, addr := range addrs { + ip := addr.(*net.IPNet).IP + if ip.To4() != nil { + return ip, nil + } + } + + return nil, errors.New("no ipv4 addr") +} + +func defaultRouteInterfaceIndex() (int, error) { + rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) + if err != nil { + return 0, fmt.Errorf("route.FetchRIB: %w", err) + } + msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) + if err != nil { + return 0, fmt.Errorf("route.ParseRIB: %w", err) + } + for _, message := range msgs { + routeMessage := message.(*route.RouteMessage) + if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 { + continue + } + + addresses := routeMessage.Addrs + + destination, ok := addresses[0].(*route.Inet4Addr) + if !ok { + continue + } + + if destination.IP != [4]byte{0, 0, 0, 0} { + continue + } + + switch addresses[1].(type) { + case *route.Inet4Addr: + return routeMessage.Index, nil + default: + continue + } + } + + return 0, fmt.Errorf("ambiguous gateway interfaces found") +} diff --git a/test/util_other_test.go b/test/util_other_test.go new file mode 100644 index 000000000..13ba87f33 --- /dev/null +++ b/test/util_other_test.go @@ -0,0 +1,13 @@ +//go:build !darwin +// +build !darwin + +package main + +import ( + "errors" + "net" +) + +func defaultRouteIP() (net.IP, error) { + return nil, errors.New("not supported") +} diff --git a/test/vmess_test.go b/test/vmess_test.go new file mode 100644 index 000000000..1483cf0af --- /dev/null +++ b/test/vmess_test.go @@ -0,0 +1,466 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/Dreamacro/clash/adapter/outbound" + C "github.com/Dreamacro/clash/constant" + + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" +) + +func TestClash_Vmess(t *testing.T) { + configPath := C.Path.Resolve("vmess.json") + + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)}, + } + + id, err := startContainer(cfg, hostCfg, "vmess") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessAEAD(t *testing.T) { + configPath := C.Path.Resolve("vmess-aead.json") + + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)}, + } + + id, err := startContainer(cfg, hostCfg, "vmess-aead") + if err != nil { + assert.FailNow(t, err.Error()) + } + + t.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 0, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-tls.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-tls") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + TLS: true, + SkipCertVerify: true, + ServerName: "example.org", + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessHTTP2(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-http2.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-http2") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "h2", + TLS: true, + SkipCertVerify: true, + ServerName: "example.org", + UDP: true, + HTTP2Opts: outbound.HTTP2Options{ + Host: []string{"example.org"}, + Path: "/test", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessHTTP(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-http.json")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-http") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "http", + UDP: true, + HTTPOpts: outbound.HTTPOptions{ + Method: "GET", + Path: []string{"/"}, + Headers: map[string][]string{ + "Host": {"www.amazon.com"}, + "User-Agent": { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 Edg/84.0.522.49", + }, + "Accept-Encoding": { + "gzip, deflate", + }, + "Connection": { + "keep-alive", + }, + "Pragma": {"no-cache"}, + }, + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessWebsocket(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-ws.json")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-ws") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "ws", + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessWebsocketTLS(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-ws-tls.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-ws") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "ws", + TLS: true, + SkipCertVerify: true, + UDP: true, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessGrpc(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-grpc.json")), + fmt.Sprintf("%s:/etc/ssl/v2ray/fullchain.pem", C.Path.Resolve("example.org.pem")), + fmt.Sprintf("%s:/etc/ssl/v2ray/privkey.pem", C.Path.Resolve("example.org-key.pem")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-grpc") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "grpc", + TLS: true, + SkipCertVerify: true, + UDP: true, + ServerName: "example.org", + GrpcOpts: outbound.GrpcOptions{ + GrpcServiceName: "example!", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessWebsocket0RTT(t *testing.T) { + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/v2ray/config.json", C.Path.Resolve("vmess-ws-0rtt.json")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "ws", + UDP: true, + ServerName: "example.org", + WSOpts: outbound.WSOptions{ + MaxEarlyData: 2048, + EarlyDataHeaderName: "Sec-WebSocket-Protocol", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func TestClash_VmessWebsocketXray0RTT(t *testing.T) { + cfg := &container.Config{ + Image: ImageXray, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{ + fmt.Sprintf("%s:/etc/xray/config.json", C.Path.Resolve("vmess-ws-0rtt.json")), + }, + } + + id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt") + if err != nil { + assert.FailNow(t, err.Error()) + } + defer cleanContainer(id) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 32, + Network: "ws", + UDP: true, + ServerName: "example.org", + WSOpts: outbound.WSOptions{ + Path: "/?ed=2048", + }, + }) + if err != nil { + assert.FailNow(t, err.Error()) + } + + time.Sleep(waitTime) + testSuit(t, proxy) +} + +func Benchmark_Vmess(b *testing.B) { + configPath := C.Path.Resolve("vmess-aead.json") + + cfg := &container.Config{ + Image: ImageVmess, + ExposedPorts: defaultExposedPorts, + } + hostCfg := &container.HostConfig{ + PortBindings: defaultPortBindings, + Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)}, + } + + id, err := startContainer(cfg, hostCfg, "vmess-aead") + if err != nil { + assert.FailNow(b, err.Error()) + } + + b.Cleanup(func() { + cleanContainer(id) + }) + + proxy, err := outbound.NewVmess(outbound.VmessOption{ + Name: "vmess", + Server: localIP.String(), + Port: 10002, + UUID: "b831381d-6324-4d53-ad4f-8cda48b30811", + Cipher: "auto", + AlterID: 0, + UDP: true, + }) + if err != nil { + assert.FailNow(b, err.Error()) + } + + time.Sleep(waitTime) + benchmarkProxy(b, proxy) +} diff --git a/component/gun/gun.go b/transport/gun/gun.go similarity index 93% rename from component/gun/gun.go rename to transport/gun/gun.go index f19006adf..f6f761166 100644 --- a/component/gun/gun.go +++ b/transport/gun/gun.go @@ -5,7 +5,6 @@ package gun import ( "bufio" - "bytes" "crypto/tls" "encoding/binary" "errors" @@ -17,6 +16,8 @@ import ( "sync" "time" + "github.com/Dreamacro/clash/common/pool" + "go.uber.org/atomic" "golang.org/x/net/http2" ) @@ -26,13 +27,10 @@ var ( ErrSmallBuffer = errors.New("buffer too small") ) -var ( - defaultHeader = http.Header{ - "content-type": []string{"application/grpc"}, - "user-agent": []string{"grpc-go/1.36.0"}, - } - bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} -) +var defaultHeader = http.Header{ + "content-type": []string{"application/grpc"}, + "user-agent": []string{"grpc-go/1.36.0"}, +} type DialFn = func(network, addr string) (net.Conn, error) @@ -127,9 +125,8 @@ func (g *Conn) Write(b []byte) (n int, err error) { grpcPayloadLen := uint32(varuintSize + 1 + len(b)) binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen) - buf := bufferPool.Get().(*bytes.Buffer) - defer bufferPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) buf.Write(grpcHeader) buf.Write(protobufHeader[:varuintSize+1]) buf.Write(b) @@ -211,6 +208,8 @@ func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn, Scheme: "https", Host: cfg.Host, Path: fmt.Sprintf("/%s/Tun", serviceName), + // for unescape path + Opaque: fmt.Sprintf("//%s/%s/Tun", cfg.Host, serviceName), }, Proto: "HTTP/2", ProtoMajor: 2, diff --git a/component/simple-obfs/http.go b/transport/simple-obfs/http.go similarity index 100% rename from component/simple-obfs/http.go rename to transport/simple-obfs/http.go diff --git a/component/simple-obfs/tls.go b/transport/simple-obfs/tls.go similarity index 98% rename from component/simple-obfs/tls.go rename to transport/simple-obfs/tls.go index 6484eb6c4..1c609c15a 100644 --- a/component/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -78,6 +78,7 @@ func (to *TLSObfs) Read(b []byte) (int, error) { // type + ver = 3 return to.read(b, 3) } + func (to *TLSObfs) Write(b []byte) (int, error) { length := len(b) for i := 0; i < length; i += chunkSize { @@ -102,7 +103,8 @@ func (to *TLSObfs) write(b []byte) (int, error) { return len(b), err } - buf := &bytes.Buffer{} + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) buf.Write([]byte{0x17, 0x03, 0x03}) binary.Write(buf, binary.BigEndian, uint16(len(b))) buf.Write(b) diff --git a/component/snell/cipher.go b/transport/snell/cipher.go similarity index 99% rename from component/snell/cipher.go rename to transport/snell/cipher.go index f778e6471..0f31aea53 100644 --- a/component/snell/cipher.go +++ b/transport/snell/cipher.go @@ -20,6 +20,7 @@ func (sc *snellCipher) SaltSize() int { return 16 } func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) } + func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) } diff --git a/component/snell/pool.go b/transport/snell/pool.go similarity index 100% rename from component/snell/pool.go rename to transport/snell/pool.go diff --git a/component/snell/snell.go b/transport/snell/snell.go similarity index 91% rename from component/snell/snell.go rename to transport/snell/snell.go index ecbc90ee8..64807b819 100644 --- a/component/snell/snell.go +++ b/transport/snell/snell.go @@ -1,13 +1,13 @@ package snell import ( - "bytes" "encoding/binary" "errors" "fmt" "io" "net" - "sync" + + "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/go-shadowsocks2/shadowaead" ) @@ -30,10 +30,7 @@ const ( Version byte = 1 ) -var ( - bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} - endSignal = []byte{} -) +var endSignal = []byte{} type Snell struct { net.Conn @@ -79,9 +76,8 @@ func (s *Snell) Read(b []byte) (int, error) { } func WriteHeader(conn net.Conn, host string, port uint, version int) error { - buf := bufferPool.Get().(*bytes.Buffer) - buf.Reset() - defer bufferPool.Put(buf) + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) buf.WriteByte(Version) if version == Version2 { buf.WriteByte(CommandConnectV2) diff --git a/transport/socks4/socks4.go b/transport/socks4/socks4.go new file mode 100644 index 000000000..a29416241 --- /dev/null +++ b/transport/socks4/socks4.go @@ -0,0 +1,199 @@ +package socks4 + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net" + "strconv" + + "github.com/Dreamacro/clash/component/auth" +) + +const Version = 0x04 + +type Command = uint8 + +const ( + CmdConnect Command = 0x01 + CmdBind Command = 0x02 +) + +type Code = uint8 + +const ( + RequestGranted Code = 90 + RequestRejected Code = 91 + RequestIdentdFailed Code = 92 + RequestIdentdMismatched Code = 93 +) + +var ( + errVersionMismatched = errors.New("version code mismatched") + errCommandNotSupported = errors.New("command not supported") + errIPv6NotSupported = errors.New("IPv6 not supported") + + ErrRequestRejected = errors.New("request rejected or failed") + ErrRequestIdentdFailed = errors.New("request rejected because SOCKS server cannot connect to identd on the client") + ErrRequestIdentdMismatched = errors.New("request rejected because the client program and identd report different user-ids") + ErrRequestUnknownCode = errors.New("request failed with unknown code") +) + +func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) { + var req [8]byte + if _, err = io.ReadFull(rw, req[:]); err != nil { + return + } + + if req[0] != Version { + err = errVersionMismatched + return + } + + if command = req[1]; command != CmdConnect { + err = errCommandNotSupported + return + } + + var ( + dstIP = req[4:8] // [4]byte + dstPort = req[2:4] // [2]byte + ) + + var ( + host string + port string + code uint8 + userID []byte + ) + if userID, err = readUntilNull(rw); err != nil { + return + } + + if isReservedIP(dstIP) { + var target []byte + if target, err = readUntilNull(rw); err != nil { + return + } + host = string(target) + } + + port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort))) + if host != "" { + addr = net.JoinHostPort(host, port) + } else { + addr = net.JoinHostPort(net.IP(dstIP).String(), port) + } + + // SOCKS4 only support USERID auth. + if authenticator == nil || authenticator.Verify(string(userID), "") { + code = RequestGranted + } else { + code = RequestIdentdMismatched + err = ErrRequestIdentdMismatched + } + + var reply [8]byte + reply[0] = 0x00 // reply code + reply[1] = code // result code + copy(reply[4:8], dstIP) + copy(reply[2:4], dstPort) + + _, wErr := rw.Write(reply[:]) + if err == nil { + err = wErr + } + return +} + +func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) { + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return err + } + + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return err + } + + ip := net.ParseIP(host) + if ip == nil /* HOST */ { + ip = net.IPv4(0, 0, 0, 1).To4() + } else if ip.To4() == nil /* IPv6 */ { + return errIPv6NotSupported + } + + dstIP := ip.To4() + + req := &bytes.Buffer{} + req.WriteByte(Version) + req.WriteByte(command) + binary.Write(req, binary.BigEndian, uint16(port)) + req.Write(dstIP) + req.WriteString(userID) + req.WriteByte(0) /* NULL */ + + if isReservedIP(dstIP) /* SOCKS4A */ { + req.WriteString(host) + req.WriteByte(0) /* NULL */ + } + + if _, err = rw.Write(req.Bytes()); err != nil { + return err + } + + var resp [8]byte + if _, err = io.ReadFull(rw, resp[:]); err != nil { + return err + } + + if resp[0] != 0x00 { + return errVersionMismatched + } + + switch resp[1] { + case RequestGranted: + return nil + case RequestRejected: + return ErrRequestRejected + case RequestIdentdFailed: + return ErrRequestIdentdFailed + case RequestIdentdMismatched: + return ErrRequestIdentdMismatched + default: + return ErrRequestUnknownCode + } +} + +// For version 4A, if the client cannot resolve the destination host's +// domain name to find its IP address, it should set the first three bytes +// of DSTIP to NULL and the last byte to a non-zero value. (This corresponds +// to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The +// Internet Assigned Numbers Authority -- such an address is inadmissible +// as a destination IP address and thus should never occur if the client +// can resolve the domain name.) +func isReservedIP(ip net.IP) bool { + subnet := net.IPNet{ + IP: net.IPv4zero, + Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00), + } + + return !ip.IsUnspecified() && subnet.Contains(ip) +} + +func readUntilNull(r io.Reader) ([]byte, error) { + buf := &bytes.Buffer{} + var data [1]byte + + for { + if _, err := r.Read(data[:]); err != nil { + return nil, err + } + if data[0] == 0 { + return buf.Bytes(), nil + } + buf.WriteByte(data[0]) + } +} diff --git a/component/socks5/socks5.go b/transport/socks5/socks5.go similarity index 100% rename from component/socks5/socks5.go rename to transport/socks5/socks5.go diff --git a/component/ssr/obfs/base.go b/transport/ssr/obfs/base.go similarity index 100% rename from component/ssr/obfs/base.go rename to transport/ssr/obfs/base.go diff --git a/component/ssr/obfs/http_post.go b/transport/ssr/obfs/http_post.go similarity index 100% rename from component/ssr/obfs/http_post.go rename to transport/ssr/obfs/http_post.go diff --git a/component/ssr/obfs/http_simple.go b/transport/ssr/obfs/http_simple.go similarity index 99% rename from component/ssr/obfs/http_simple.go rename to transport/ssr/obfs/http_simple.go index 86dea9475..c1ea76738 100644 --- a/component/ssr/obfs/http_simple.go +++ b/transport/ssr/obfs/http_simple.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" ) func init() { @@ -102,9 +101,8 @@ func (c *httpConn) Write(b []byte) (int, error) { hosts := strings.Split(host, ",") host = hosts[rand.Intn(len(hosts))] - buf := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) if c.post { buf.WriteString("POST /") } else { diff --git a/component/ssr/obfs/obfs.go b/transport/ssr/obfs/obfs.go similarity index 100% rename from component/ssr/obfs/obfs.go rename to transport/ssr/obfs/obfs.go diff --git a/component/ssr/obfs/plain.go b/transport/ssr/obfs/plain.go similarity index 100% rename from component/ssr/obfs/plain.go rename to transport/ssr/obfs/plain.go diff --git a/component/ssr/obfs/random_head.go b/transport/ssr/obfs/random_head.go similarity index 100% rename from component/ssr/obfs/random_head.go rename to transport/ssr/obfs/random_head.go diff --git a/component/ssr/obfs/tls1.2_ticket_auth.go b/transport/ssr/obfs/tls1.2_ticket_auth.go similarity index 90% rename from component/ssr/obfs/tls1.2_ticket_auth.go rename to transport/ssr/obfs/tls1.2_ticket_auth.go index f3c5b456d..10f2786ad 100644 --- a/component/ssr/obfs/tls1.2_ticket_auth.go +++ b/transport/ssr/obfs/tls1.2_ticket_auth.go @@ -10,7 +10,7 @@ import ( "time" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/transport/ssr/tools" ) func init() { @@ -87,9 +87,8 @@ func (c *tls12TicketConn) Read(b []byte) (int, error) { func (c *tls12TicketConn) Write(b []byte) (int, error) { length := len(b) if c.handshakeStatus == 8 { - buf := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) for len(b) > 2048 { size := rand.Intn(4096) + 100 if len(b) < size { @@ -115,9 +114,8 @@ func (c *tls12TicketConn) Write(b []byte) (int, error) { if c.handshakeStatus == 0 { c.handshakeStatus = 1 - data := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(data) - defer data.Reset() + data := pool.GetBuffer() + defer pool.PutBuffer(data) data.Write([]byte{3, 3}) c.packAuthData(data) @@ -126,9 +124,8 @@ func (c *tls12TicketConn) Write(b []byte) (int, error) { data.Write([]byte{0x00, 0x1c, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x0a}) data.Write([]byte{0x1, 0x0}) - ext := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(ext) - defer ext.Reset() + ext := pool.GetBuffer() + defer pool.PutBuffer(ext) host := c.getHost() ext.Write([]byte{0xff, 0x01, 0x00, 0x01, 0x00}) @@ -145,9 +142,8 @@ func (c *tls12TicketConn) Write(b []byte) (int, error) { binary.Write(data, binary.BigEndian, uint16(ext.Len())) data.ReadFrom(ext) - ret := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(ret) - defer ret.Reset() + ret := pool.GetBuffer() + defer pool.PutBuffer(ret) ret.Write([]byte{0x16, 3, 1}) binary.Write(ret, binary.BigEndian, uint16(data.Len()+4)) @@ -161,9 +157,8 @@ func (c *tls12TicketConn) Write(b []byte) (int, error) { } return length, nil } else if c.handshakeStatus == 1 && len(b) == 0 { - buf := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) buf.Write([]byte{0x14, 3, 3, 0, 1, 1, 0x16, 3, 3, 0, 0x20}) tools.AppendRandBytes(buf, 22) diff --git a/component/ssr/protocol/auth_aes128_md5.go b/transport/ssr/protocol/auth_aes128_md5.go similarity index 87% rename from component/ssr/protocol/auth_aes128_md5.go rename to transport/ssr/protocol/auth_aes128_md5.go index 08e350c46..d3bc94172 100644 --- a/component/ssr/protocol/auth_aes128_md5.go +++ b/transport/ssr/protocol/auth_aes128_md5.go @@ -1,6 +1,6 @@ package protocol -import "github.com/Dreamacro/clash/component/ssr/tools" +import "github.com/Dreamacro/clash/transport/ssr/tools" func init() { register("auth_aes128_md5", newAuthAES128MD5, 9) diff --git a/component/ssr/protocol/auth_aes128_sha1.go b/transport/ssr/protocol/auth_aes128_sha1.go similarity index 96% rename from component/ssr/protocol/auth_aes128_sha1.go rename to transport/ssr/protocol/auth_aes128_sha1.go index 503834562..8ce57d288 100644 --- a/component/ssr/protocol/auth_aes128_sha1.go +++ b/transport/ssr/protocol/auth_aes128_sha1.go @@ -10,12 +10,14 @@ import ( "strings" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/ssr/tools" ) -type hmacMethod func(key, data []byte) []byte -type hashDigestMethod func([]byte) []byte +type ( + hmacMethod func(key, data []byte) []byte + hashDigestMethod func([]byte) []byte +) func init() { register("auth_aes128_sha1", newAuthAES128SHA1, 9) @@ -152,7 +154,7 @@ func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error { } func (a *authAES128) DecodePacket(b []byte) ([]byte, error) { - if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { + if !bytes.Equal(a.hmac(a.userKey, b[:len(b)-4])[:4], b[len(b)-4:]) { return nil, errAuthAES128ChksumError } return b[:len(b)-4], nil diff --git a/component/ssr/protocol/auth_chain_a.go b/transport/ssr/protocol/auth_chain_a.go similarity index 98% rename from component/ssr/protocol/auth_chain_a.go rename to transport/ssr/protocol/auth_chain_a.go index db51e1048..906f8deb3 100644 --- a/component/ssr/protocol/auth_chain_a.go +++ b/transport/ssr/protocol/auth_chain_a.go @@ -12,8 +12,8 @@ import ( "strings" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/ssr/tools" "github.com/Dreamacro/go-shadowsocks2/core" ) @@ -278,7 +278,7 @@ func getRandStartPos(length int, random *tools.XorShift128Plus) int { if length == 0 { return 0 } - return int(random.Next()%8589934609) % length + return int(int64(random.Next()%8589934609) % int64(length)) } func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int { diff --git a/component/ssr/protocol/auth_chain_b.go b/transport/ssr/protocol/auth_chain_b.go similarity index 97% rename from component/ssr/protocol/auth_chain_b.go rename to transport/ssr/protocol/auth_chain_b.go index 2f3f8f17a..857b2a3aa 100644 --- a/component/ssr/protocol/auth_chain_b.go +++ b/transport/ssr/protocol/auth_chain_b.go @@ -4,7 +4,7 @@ import ( "net" "sort" - "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/transport/ssr/tools" ) func init() { diff --git a/component/ssr/protocol/auth_sha1_v4.go b/transport/ssr/protocol/auth_sha1_v4.go similarity index 98% rename from component/ssr/protocol/auth_sha1_v4.go rename to transport/ssr/protocol/auth_sha1_v4.go index 0f24c360c..30392c9e7 100644 --- a/component/ssr/protocol/auth_sha1_v4.go +++ b/transport/ssr/protocol/auth_sha1_v4.go @@ -9,7 +9,7 @@ import ( "net" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/transport/ssr/tools" ) func init() { diff --git a/component/ssr/protocol/base.go b/transport/ssr/protocol/base.go similarity index 100% rename from component/ssr/protocol/base.go rename to transport/ssr/protocol/base.go diff --git a/component/ssr/protocol/origin.go b/transport/ssr/protocol/origin.go similarity index 100% rename from component/ssr/protocol/origin.go rename to transport/ssr/protocol/origin.go diff --git a/component/ssr/protocol/packet.go b/transport/ssr/protocol/packet.go similarity index 79% rename from component/ssr/protocol/packet.go rename to transport/ssr/protocol/packet.go index 1577c7885..249db70a2 100644 --- a/component/ssr/protocol/packet.go +++ b/transport/ssr/protocol/packet.go @@ -1,10 +1,9 @@ package protocol import ( - "bytes" "net" - "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/common/pool" ) type PacketConn struct { @@ -13,9 +12,8 @@ type PacketConn struct { } func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { - buf := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) err := c.EncodePacket(buf, b) if err != nil { return 0, err diff --git a/component/ssr/protocol/protocol.go b/transport/ssr/protocol/protocol.go similarity index 100% rename from component/ssr/protocol/protocol.go rename to transport/ssr/protocol/protocol.go diff --git a/component/ssr/protocol/stream.go b/transport/ssr/protocol/stream.go similarity index 84% rename from component/ssr/protocol/stream.go rename to transport/ssr/protocol/stream.go index 53f53ead4..3c846157a 100644 --- a/component/ssr/protocol/stream.go +++ b/transport/ssr/protocol/stream.go @@ -5,7 +5,6 @@ import ( "net" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" ) type Conn struct { @@ -37,9 +36,8 @@ func (c *Conn) Read(b []byte) (int, error) { func (c *Conn) Write(b []byte) (int, error) { bLength := len(b) - buf := tools.BufPool.Get().(*bytes.Buffer) - defer tools.BufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) err := c.Encode(buf, b) if err != nil { return 0, err diff --git a/transport/ssr/tools/bufPool.go b/transport/ssr/tools/bufPool.go new file mode 100644 index 000000000..ac15c97db --- /dev/null +++ b/transport/ssr/tools/bufPool.go @@ -0,0 +1,11 @@ +package tools + +import ( + "bytes" + "crypto/rand" + "io" +) + +func AppendRandBytes(b *bytes.Buffer, length int) { + b.ReadFrom(io.LimitReader(rand.Reader, int64(length))) +} diff --git a/component/ssr/tools/crypto.go b/transport/ssr/tools/crypto.go similarity index 100% rename from component/ssr/tools/crypto.go rename to transport/ssr/tools/crypto.go diff --git a/component/ssr/tools/random.go b/transport/ssr/tools/random.go similarity index 100% rename from component/ssr/tools/random.go rename to transport/ssr/tools/random.go diff --git a/component/trojan/trojan.go b/transport/trojan/trojan.go similarity index 75% rename from component/trojan/trojan.go rename to transport/trojan/trojan.go index ef440d62b..9d9a33b99 100644 --- a/component/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -1,7 +1,6 @@ package trojan import ( - "bytes" "crypto/sha256" "crypto/tls" "encoding/binary" @@ -9,9 +8,12 @@ import ( "errors" "io" "net" + "net/http" "sync" - "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/socks5" + "github.com/Dreamacro/clash/transport/vmess" ) const ( @@ -20,10 +22,10 @@ const ( ) var ( - defaultALPN = []string{"h2", "http/1.1"} - crlf = []byte{'\r', '\n'} + defaultALPN = []string{"h2", "http/1.1"} + defaultWebsocketALPN = []string{"http/1.1"} - bufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} + crlf = []byte{'\r', '\n'} ) type Command = byte @@ -34,11 +36,17 @@ var ( ) type Option struct { - Password string - ALPN []string - ServerName string - SkipCertVerify bool - ClientSessionCache tls.ClientSessionCache + Password string + ALPN []string + ServerName string + SkipCertVerify bool +} + +type WebsocketOption struct { + Host string + Port string + Path string + Headers http.Header } type Trojan struct { @@ -57,7 +65,6 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { MinVersion: tls.VersionTLS12, InsecureSkipVerify: t.option.SkipCertVerify, ServerName: t.option.ServerName, - ClientSessionCache: t.option.ClientSessionCache, } tlsConn := tls.Client(conn, tlsConfig) @@ -68,10 +75,32 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { return tlsConn, nil } +func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) { + alpn := defaultWebsocketALPN + if len(t.option.ALPN) != 0 { + alpn = t.option.ALPN + } + + tlsConfig := &tls.Config{ + NextProtos: alpn, + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: t.option.SkipCertVerify, + ServerName: t.option.ServerName, + } + + return vmess.StreamWebsocketConn(conn, &vmess.WebsocketConfig{ + Host: wsOptions.Host, + Port: wsOptions.Port, + Path: wsOptions.Path, + Headers: wsOptions.Headers, + TLS: true, + TLSConfig: tlsConfig, + }) +} + func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error { - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) buf.Write(t.hexPassword) buf.Write(crlf) @@ -91,9 +120,8 @@ func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn { } func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - defer buf.Reset() + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) buf.Write(socks5Addr) binary.Write(buf, binary.BigEndian, uint16(len(payload))) diff --git a/component/v2ray-plugin/mux.go b/transport/v2ray-plugin/mux.go similarity index 100% rename from component/v2ray-plugin/mux.go rename to transport/v2ray-plugin/mux.go diff --git a/component/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go similarity index 63% rename from component/v2ray-plugin/websocket.go rename to transport/v2ray-plugin/websocket.go index fbd1f3e32..7591b4a86 100644 --- a/component/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -5,7 +5,7 @@ import ( "net" "net/http" - "github.com/Dreamacro/clash/component/vmess" + "github.com/Dreamacro/clash/transport/vmess" ) // Option is options of websocket obfs @@ -16,7 +16,6 @@ type Option struct { Headers map[string]string TLS bool SkipCertVerify bool - SessionCache tls.ClientSessionCache Mux bool } @@ -28,13 +27,22 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { } config := &vmess.WebsocketConfig{ - Host: option.Host, - Port: option.Port, - Path: option.Path, - TLS: option.TLS, - Headers: header, - SkipCertVerify: option.SkipCertVerify, - SessionCache: option.SessionCache, + Host: option.Host, + Port: option.Port, + Path: option.Path, + Headers: header, + } + + if option.TLS { + config.TLS = true + config.TLSConfig = &tls.Config{ + ServerName: option.Host, + InsecureSkipVerify: option.SkipCertVerify, + NextProtos: []string{"http/1.1"}, + } + if host := config.Headers.Get("Host"); host != "" { + config.TLSConfig.ServerName = host + } } var err error diff --git a/component/vmess/aead.go b/transport/vmess/aead.go similarity index 100% rename from component/vmess/aead.go rename to transport/vmess/aead.go diff --git a/component/vmess/chunk.go b/transport/vmess/chunk.go similarity index 100% rename from component/vmess/chunk.go rename to transport/vmess/chunk.go diff --git a/component/vmess/conn.go b/transport/vmess/conn.go similarity index 98% rename from component/vmess/conn.go rename to transport/vmess/conn.go index e6e57be61..cc3155ee4 100644 --- a/component/vmess/conn.go +++ b/transport/vmess/conn.go @@ -59,12 +59,12 @@ func (vc *Conn) Read(b []byte) (int, error) { func (vc *Conn) sendRequest() error { timestamp := time.Now() + mbuf := &bytes.Buffer{} + if !vc.isAead { h := hmac.New(md5.New, vc.id.UUID.Bytes()) binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) - if _, err := vc.Conn.Write(h.Sum(nil)); err != nil { - return err - } + mbuf.Write(h.Sum(nil)) } buf := &bytes.Buffer{} @@ -110,7 +110,8 @@ func (vc *Conn) sendRequest() error { stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp)) stream.XORKeyStream(buf.Bytes(), buf.Bytes()) - _, err = vc.Conn.Write(buf.Bytes()) + mbuf.Write(buf.Bytes()) + _, err = vc.Conn.Write(mbuf.Bytes()) return err } diff --git a/component/vmess/h2.go b/transport/vmess/h2.go similarity index 100% rename from component/vmess/h2.go rename to transport/vmess/h2.go diff --git a/component/vmess/header.go b/transport/vmess/header.go similarity index 99% rename from component/vmess/header.go rename to transport/vmess/header.go index 27a734bed..67d407e6f 100644 --- a/component/vmess/header.go +++ b/transport/vmess/header.go @@ -92,7 +92,7 @@ func sealVMessAEADHeader(key [16]byte, data []byte, t time.Time) []byte { payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:]) } - var outputBuffer = &bytes.Buffer{} + outputBuffer := &bytes.Buffer{} outputBuffer.Write(generatedAuthID[:]) outputBuffer.Write(payloadHeaderLengthAEADEncrypted) diff --git a/component/vmess/http.go b/transport/vmess/http.go similarity index 89% rename from component/vmess/http.go rename to transport/vmess/http.go index 5c1a5bd1c..1c09e215a 100644 --- a/component/vmess/http.go +++ b/transport/vmess/http.go @@ -52,7 +52,12 @@ func (hc *httpConn) Write(b []byte) (int, error) { } path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] - u := fmt.Sprintf("http://%s%s", hc.cfg.Host, path) + host := hc.cfg.Host + if header := hc.cfg.Headers["Host"]; len(header) != 0 { + host = header[rand.Intn(len(header))] + } + + u := fmt.Sprintf("http://%s%s", host, path) req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b)) for key, list := range hc.cfg.Headers { req.Header.Set(key, list[rand.Intn(len(list))]) diff --git a/component/vmess/tls.go b/transport/vmess/tls.go similarity index 85% rename from component/vmess/tls.go rename to transport/vmess/tls.go index b003a7533..234c31477 100644 --- a/component/vmess/tls.go +++ b/transport/vmess/tls.go @@ -8,7 +8,6 @@ import ( type TLSConfig struct { Host string SkipCertVerify bool - SessionCache tls.ClientSessionCache NextProtos []string } @@ -16,7 +15,6 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { tlsConfig := &tls.Config{ ServerName: cfg.Host, InsecureSkipVerify: cfg.SkipCertVerify, - ClientSessionCache: cfg.SessionCache, NextProtos: cfg.NextProtos, } diff --git a/component/vmess/user.go b/transport/vmess/user.go similarity index 100% rename from component/vmess/user.go rename to transport/vmess/user.go diff --git a/component/vmess/vmess.go b/transport/vmess/vmess.go similarity index 100% rename from component/vmess/vmess.go rename to transport/vmess/vmess.go diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go new file mode 100644 index 000000000..f769dcce8 --- /dev/null +++ b/transport/vmess/websocket.go @@ -0,0 +1,313 @@ +package vmess + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +type websocketConn struct { + conn *websocket.Conn + reader io.Reader + remoteAddr net.Addr + + // https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency + rMux sync.Mutex + wMux sync.Mutex +} + +type websocketWithEarlyDataConn struct { + net.Conn + underlay net.Conn + closed bool + dialed chan bool + cancel context.CancelFunc + ctx context.Context + config *WebsocketConfig +} + +type WebsocketConfig struct { + Host string + Port string + Path string + Headers http.Header + TLS bool + TLSConfig *tls.Config + MaxEarlyData int + EarlyDataHeaderName string +} + +// Read implements net.Conn.Read() +func (wsc *websocketConn) Read(b []byte) (int, error) { + wsc.rMux.Lock() + defer wsc.rMux.Unlock() + for { + reader, err := wsc.getReader() + if err != nil { + return 0, err + } + + nBytes, err := reader.Read(b) + if err == io.EOF { + wsc.reader = nil + continue + } + return nBytes, err + } +} + +// Write implements io.Writer. +func (wsc *websocketConn) Write(b []byte) (int, error) { + wsc.wMux.Lock() + defer wsc.wMux.Unlock() + if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { + return 0, err + } + return len(b), nil +} + +func (wsc *websocketConn) Close() error { + var errors []string + if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { + errors = append(errors, err.Error()) + } + if err := wsc.conn.Close(); err != nil { + errors = append(errors, err.Error()) + } + if len(errors) > 0 { + return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ",")) + } + return nil +} + +func (wsc *websocketConn) getReader() (io.Reader, error) { + if wsc.reader != nil { + return wsc.reader, nil + } + + _, reader, err := wsc.conn.NextReader() + if err != nil { + return nil, err + } + wsc.reader = reader + return reader, nil +} + +func (wsc *websocketConn) LocalAddr() net.Addr { + return wsc.conn.LocalAddr() +} + +func (wsc *websocketConn) RemoteAddr() net.Addr { + return wsc.remoteAddr +} + +func (wsc *websocketConn) SetDeadline(t time.Time) error { + if err := wsc.SetReadDeadline(t); err != nil { + return err + } + return wsc.SetWriteDeadline(t) +} + +func (wsc *websocketConn) SetReadDeadline(t time.Time) error { + return wsc.conn.SetReadDeadline(t) +} + +func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { + return wsc.conn.SetWriteDeadline(t) +} + +func (wsedc *websocketWithEarlyDataConn) Dial(earlyData []byte) error { + base64DataBuf := &bytes.Buffer{} + base64EarlyDataEncoder := base64.NewEncoder(base64.RawURLEncoding, base64DataBuf) + + earlyDataBuf := bytes.NewBuffer(earlyData) + if _, err := base64EarlyDataEncoder.Write(earlyDataBuf.Next(wsedc.config.MaxEarlyData)); err != nil { + return errors.New("failed to encode early data: " + err.Error()) + } + + if errc := base64EarlyDataEncoder.Close(); errc != nil { + return errors.New("failed to encode early data tail: " + errc.Error()) + } + + var err error + if wsedc.Conn, err = streamWebsocketConn(wsedc.underlay, wsedc.config, base64DataBuf); err != nil { + wsedc.Close() + return errors.New("failed to dial WebSocket: " + err.Error()) + } + + wsedc.dialed <- true + if earlyDataBuf.Len() != 0 { + _, err = wsedc.Conn.Write(earlyDataBuf.Bytes()) + } + + return err +} + +func (wsedc *websocketWithEarlyDataConn) Write(b []byte) (int, error) { + if wsedc.closed { + return 0, io.ErrClosedPipe + } + if wsedc.Conn == nil { + if err := wsedc.Dial(b); err != nil { + return 0, err + } + return len(b), nil + } + + return wsedc.Conn.Write(b) +} + +func (wsedc *websocketWithEarlyDataConn) Read(b []byte) (int, error) { + if wsedc.closed { + return 0, io.ErrClosedPipe + } + if wsedc.Conn == nil { + select { + case <-wsedc.ctx.Done(): + return 0, io.ErrUnexpectedEOF + case <-wsedc.dialed: + } + } + return wsedc.Conn.Read(b) +} + +func (wsedc *websocketWithEarlyDataConn) Close() error { + wsedc.closed = true + wsedc.cancel() + if wsedc.Conn == nil { + return nil + } + return wsedc.Conn.Close() +} + +func (wsedc *websocketWithEarlyDataConn) LocalAddr() net.Addr { + if wsedc.Conn == nil { + return wsedc.underlay.LocalAddr() + } + return wsedc.Conn.LocalAddr() +} + +func (wsedc *websocketWithEarlyDataConn) RemoteAddr() net.Addr { + if wsedc.Conn == nil { + return wsedc.underlay.RemoteAddr() + } + return wsedc.Conn.RemoteAddr() +} + +func (wsedc *websocketWithEarlyDataConn) SetDeadline(t time.Time) error { + if err := wsedc.SetReadDeadline(t); err != nil { + return err + } + return wsedc.SetWriteDeadline(t) +} + +func (wsedc *websocketWithEarlyDataConn) SetReadDeadline(t time.Time) error { + if wsedc.Conn == nil { + return nil + } + return wsedc.Conn.SetReadDeadline(t) +} + +func (wsedc *websocketWithEarlyDataConn) SetWriteDeadline(t time.Time) error { + if wsedc.Conn == nil { + return nil + } + return wsedc.Conn.SetWriteDeadline(t) +} + +func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { + ctx, cancel := context.WithCancel(context.Background()) + conn = &websocketWithEarlyDataConn{ + dialed: make(chan bool, 1), + cancel: cancel, + ctx: ctx, + underlay: conn, + config: c, + } + return conn, nil +} + +func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) { + dialer := &websocket.Dialer{ + NetDial: func(network, addr string) (net.Conn, error) { + return conn, nil + }, + ReadBufferSize: 4 * 1024, + WriteBufferSize: 4 * 1024, + HandshakeTimeout: time.Second * 8, + } + + scheme := "ws" + if c.TLS { + scheme = "wss" + dialer.TLSClientConfig = c.TLSConfig + } + + uri := url.URL{ + Scheme: scheme, + Host: net.JoinHostPort(c.Host, c.Port), + Path: c.Path, + } + + headers := http.Header{} + if c.Headers != nil { + for k := range c.Headers { + headers.Add(k, c.Headers.Get(k)) + } + } + + if earlyData != nil { + if c.EarlyDataHeaderName == "" { + uri.Path += earlyData.String() + } else { + headers.Set(c.EarlyDataHeaderName, earlyData.String()) + } + } + + wsConn, resp, err := dialer.Dial(uri.String(), headers) + if err != nil { + reason := err.Error() + if resp != nil { + reason = resp.Status + } + return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason) + } + + return &websocketConn{ + conn: wsConn, + remoteAddr: conn.RemoteAddr(), + }, nil +} + +func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { + if u, err := url.Parse(c.Path); err == nil { + if q := u.Query(); q.Get("ed") != "" { + if ed, err := strconv.Atoi(q.Get("ed")); err == nil { + c.MaxEarlyData = ed + c.EarlyDataHeaderName = "Sec-WebSocket-Protocol" + q.Del("ed") + u.RawQuery = q.Encode() + c.Path = u.String() + } + } + } + + if c.MaxEarlyData > 0 { + return streamWebsocketWithEarlyDataConn(conn, c) + } + + return streamWebsocketConn(conn, c, nil) +} diff --git a/tunnel/connection.go b/tunnel/connection.go index 24314fb6c..45de46d7f 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -1,93 +1,17 @@ package tunnel import ( - "bufio" "errors" "io" "net" - "net/http" - "strings" "time" - "github.com/Dreamacro/clash/adapters/inbound" N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" ) -func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) { - req := ctx.Request() - conn := ctx.Conn() - - // make outbound close after inbound error or close - conn = &connLinker{conn, outbound} - - inboundReader := bufio.NewReader(conn) - outboundReader := bufio.NewReader(outbound) - - inbound.RemoveExtraHTTPHostPort(req) - host := req.Host - for { - keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" - - req.RequestURI = "" - inbound.RemoveHopByHopHeaders(req.Header) - err := req.Write(outbound) - if err != nil { - break - } - - handleResponse: - // resp will be closed after we call resp.Write() - // see https://golang.org/pkg/net/http/#Response.Write - resp, err := http.ReadResponse(outboundReader, req) - if err != nil { - break - } - inbound.RemoveHopByHopHeaders(resp.Header) - - if resp.StatusCode == http.StatusContinue { - err = resp.Write(conn) - if err != nil { - break - } - goto handleResponse - } - - // close conn when header `Connection` is `close` - if resp.Header.Get("Connection") == "close" { - keepAlive = false - } - - if keepAlive { - resp.Header.Set("Proxy-Connection", "keep-alive") - resp.Header.Set("Connection", "keep-alive") - resp.Header.Set("Keep-Alive", "timeout=4") - resp.Close = false - } else { - resp.Close = true - } - err = resp.Write(conn) - if err != nil || resp.Close { - break - } - - req, err = http.ReadRequest(inboundReader) - if err != nil { - break - } - - inbound.RemoveExtraHTTPHostPort(req) - // Sometimes firefox just open a socket to process multiple domains in HTTP - // The temporary solution is close connection when encountering different HOST - if req.Host != host { - break - } - } -} - func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { defer packet.Drop() @@ -115,7 +39,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata } func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { - buf := pool.Get(pool.RelayBufferSize) + buf := pool.Get(pool.UDPBufferSize) defer pool.Put(buf) defer natTable.Delete(key) defer pc.Close() @@ -162,31 +86,3 @@ func relay(leftConn, rightConn net.Conn) { rightConn.SetReadDeadline(time.Now()) <-ch } - -// connLinker make the two net.Conn correlated, for temporary resolution of leaks. -// There is no better way to do this for now. -type connLinker struct { - net.Conn - linker net.Conn -} - -func (conn *connLinker) Read(b []byte) (n int, err error) { - n, err = conn.Conn.Read(b) - if err != nil { - conn.linker.Close() - } - return n, err -} - -func (conn *connLinker) Write(b []byte) (n int, err error) { - n, err = conn.Conn.Write(b) - if err != nil { - conn.linker.Close() - } - return n, err -} - -func (conn *connLinker) Close() error { - conn.linker.Close() - return conn.Conn.Close() -} diff --git a/tunnel/mode.go b/tunnel/mode.go index 6e07a060a..696607215 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -8,14 +8,12 @@ import ( type TunnelMode int -var ( - // ModeMapping is a mapping for Mode enum - ModeMapping = map[string]TunnelMode{ - Global.String(): Global, - Rule.String(): Rule, - Direct.String(): Direct, - } -) +// ModeMapping is a mapping for Mode enum +var ModeMapping = map[string]TunnelMode{ + Global.String(): Global, + Rule.String(): Rule, + Direct.String(): Direct, +} const ( Global TunnelMode = iota diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 77c24d889..f00a7bd02 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -1,18 +1,19 @@ package tunnel import ( + "context" "fmt" "net" "runtime" "sync" "time" - "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/component/nat" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" + "github.com/Dreamacro/clash/constant/provider" + icontext "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel/statistic" ) @@ -37,17 +38,14 @@ func init() { go process() } -// Add request to queue -func Add(ctx C.ConnContext) { - tcpQueue <- ctx +// TCPIn return fan-in queue +func TCPIn() chan<- C.ConnContext { + return tcpQueue } -// AddPacket add udp Packet to queue -func AddPacket(packet *inbound.PacketAdapter) { - select { - case udpQueue <- packet: - default: - } +// UDPIn return fan-in udp queue +func UDPIn() chan<- *inbound.PacketAdapter { + return udpQueue } // Rules return all rules @@ -100,8 +98,8 @@ func processUDP() { func process() { numUDPWorkers := 4 - if runtime.NumCPU() > numUDPWorkers { - numUDPWorkers = runtime.NumCPU() + if num := runtime.GOMAXPROCS(0); num > numUDPWorkers { + numUDPWorkers = num } for i := 0; i < numUDPWorkers; i++ { go processUDP() @@ -135,8 +133,10 @@ func preHandleMetadata(metadata *C.Metadata) error { if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName + metadata.DNSMode = C.DNSMapping if resolver.FakeIPEnabled() { metadata.DstIP = nil + metadata.DNSMode = C.DNSFakeIP } else if node := resolver.DefaultHosts.Search(host); node != nil { // redir-host should lookup the hosts metadata.DstIP = node.Data.(net.IP) @@ -212,34 +212,36 @@ func handleUDPConn(packet *inbound.PacketAdapter) { cond.Broadcast() }() - ctx := context.NewPacketConnContext(metadata) - proxy, rule, err := resolveMetadata(ctx, metadata) + pCtx := icontext.NewPacketConnContext(metadata) + proxy, rule, err := resolveMetadata(pCtx, metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) return } - rawPc, err := proxy.DialUDP(metadata) + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) + defer cancel() + rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure()) if err != nil { if rule == nil { - log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) + log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) } else { - log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) + log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.RemoteAddress(), err.Error()) } return } - ctx.InjectPacketConn(rawPc) + pCtx.InjectPacketConn(rawPc) pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule) 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 match %s(%s) using %s", metadata.SourceAddress(), metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) case mode == Global: - log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + log.Infoln("[UDP] %s --> %s using GLOBAL", metadata.SourceAddress(), metadata.RemoteAddress()) case mode == Direct: - log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[UDP] %s --> %s using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) default: - log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[UDP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) } go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) @@ -249,10 +251,10 @@ func handleUDPConn(packet *inbound.PacketAdapter) { }() } -func handleTCPConn(ctx C.ConnContext) { - defer ctx.Conn().Close() +func handleTCPConn(connCtx C.ConnContext) { + defer connCtx.Conn().Close() - metadata := ctx.Metadata() + metadata := connCtx.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) return @@ -263,18 +265,20 @@ func handleTCPConn(ctx C.ConnContext) { return } - proxy, rule, err := resolveMetadata(ctx, metadata) + proxy, rule, err := resolveMetadata(connCtx, metadata) if err != nil { log.Warnln("[Metadata] parse failed: %s", err.Error()) return } - remoteConn, err := proxy.Dial(metadata) + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) + defer cancel() + remoteConn, err := proxy.DialContext(ctx, metadata.Pure()) if err != nil { if rule == nil { - log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) + log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) } else { - log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) + log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.RemoteAddress(), err.Error()) } return } @@ -283,21 +287,16 @@ 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 match %s(%s) using %s", metadata.SourceAddress(), metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) case mode == Global: - log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + log.Infoln("[TCP] %s --> %s using GLOBAL", metadata.SourceAddress(), metadata.RemoteAddress()) case mode == Direct: - log.Infoln("[TCP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[TCP] %s --> %s using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) default: - log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) + log.Infoln("[TCP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) } - switch c := ctx.(type) { - case *context.HTTPContext: - handleHTTP(c, remoteConn) - default: - handleSocket(ctx, remoteConn) - } + handleSocket(connCtx, remoteConn) } func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {