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 {