Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
646bd4eeb4 | |||
752f87a8dc | |||
b979ff0bc2 | |||
b085addbb0 | |||
94e0e4b000 | |||
7d51ab5846 | |||
41a9488cfa | |||
51b6b8521b | |||
e5379558f6 | |||
d1fd57c432 | |||
18603c9a46 | |||
5036f62a9c | |||
2047b8eda1 | |||
0e56c195bb | |||
2b33bfae6b | |||
3fc6d55003 | |||
8eddcd77bf | |||
27dd1d7944 | |||
b1cf2ec837 | |||
84f627f302 | |||
5c03613858 | |||
1825535abd | |||
2750c7ead0 | |||
3ccd7def86 | |||
65dab4e34f | |||
5591e15452 | |||
19f809b1c8 | |||
206767247e | |||
518354e7eb | |||
86dfb6562c | |||
c0a2473160 | |||
70a19b999d | |||
e54f51af81 | |||
b068466108 | |||
b562f28c1b | |||
230e01f078 | |||
082847b403 | |||
9471d80785 | |||
b263095533 | |||
14d5137703 | |||
d8a771916a | |||
f7f30d3406 | |||
b2c9cbb43e | |||
c733f80793 | |||
88d8f93793 | |||
e57a13ed7a | |||
23525ecc15 | |||
814bd05315 | |||
e81b88fb94 | |||
c4994d6429 | |||
0740d20ba0 | |||
9eaca6e4ab | |||
609869bf5a | |||
d68339cea7 | |||
0f4cdbf187 | |||
f3f8e7e52f | |||
8d07c1eb3e | |||
46edae9896 | |||
df0ab6aa8e | |||
7b48138ad0 | |||
e9032c55fa | |||
d75cb069d9 | |||
f69f635e0b | |||
8b5e511426 | |||
6641bf7c07 | |||
afc9f3f59a | |||
a55be58c01 | |||
dcf97ff5b4 | |||
72c0af9739 | |||
b0f9c6afa8 | |||
19bb0b655c | |||
26ce3e8814 | |||
aa207ec664 | |||
c626b988a6 | |||
82c387e92b | |||
14fb789002 | |||
9071351022 | |||
f688eda2c2 | |||
2810533df4 | |||
6b7144acce | |||
e68c0d088b | |||
2c0cc374d3 | |||
86d3d77a7f | |||
14a3ff32f6 | |||
50704eaeeb | |||
38458cc4d0 | |||
b19a49335f | |||
9dda932494 | |||
6ce7b6ef83 |
96
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
96
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[Bug]"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- The English version is available. -->
|
||||||
|
感谢你向 Clash Core 提交 issue!
|
||||||
|
在提交之前,请确认:
|
||||||
|
|
||||||
|
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||||
|
- [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 Openclash、Koolclash 等)的特定问题
|
||||||
|
- [ ] 我已经使用 Clash core 的 dev 分支版本测试过,问题依旧存在
|
||||||
|
- [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧!
|
||||||
|
|
||||||
|
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thanks for submitting an issue towards the Clash core!
|
||||||
|
But before so, please do the following checklist:
|
||||||
|
|
||||||
|
- [ ] Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||||
|
- [ ] Your issue may already be reported! Please search on the [issue tracker](……/) before creating one.
|
||||||
|
- [ ] I have tested using the dev branch, and the issue still exists.
|
||||||
|
- [ ] This is an issue related to the Clash core, not to the derivatives of Clash, like Openclash or Koolclash
|
||||||
|
|
||||||
|
Please understand that we close issues that fail to follow the issue template.
|
||||||
|
-->
|
||||||
|
|
||||||
|
我都确认过了,我要继续提交。
|
||||||
|
<!-- None of the above, create a bug report -->
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
||||||
|
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
|
||||||
|
|
||||||
|
### clash core config
|
||||||
|
<!--
|
||||||
|
在下方附上 Clash core 脱敏后配置文件的内容
|
||||||
|
Paste the Clash core configuration below.
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
……
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clash log
|
||||||
|
<!--
|
||||||
|
在下方附上 Clash Core 的日志,log level 最好使用 DEBUG
|
||||||
|
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
……
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境 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
|
||||||
|
|
||||||
|
<!--
|
||||||
|
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?
|
||||||
|
-->
|
||||||
|
|
||||||
|
### 重现问题的具体布骤 Steps to Reproduce
|
||||||
|
|
||||||
|
1. [First Step]
|
||||||
|
2. [Second Step]
|
||||||
|
3. ……
|
||||||
|
|
||||||
|
**我预期会发生……?**
|
||||||
|
<!-- **Expected behavior:** [What you expected to happen] -->
|
||||||
|
|
||||||
|
**实际上发生了什麽?**
|
||||||
|
<!-- **Actual behavior:** [What actually happened] -->
|
||||||
|
|
||||||
|
### 可能的解决方案 Possible Solution
|
||||||
|
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
||||||
|
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
|
<!-- or ideas how to implement the addition or change -->
|
||||||
|
|
||||||
|
### 更多信息 More Information
|
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[Feature]"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- The English version is available. -->
|
||||||
|
感谢你向 Clash Core 提交 Feature Request!
|
||||||
|
在提交之前,请确认:
|
||||||
|
|
||||||
|
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||||
|
|
||||||
|
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thanks for submitting a feature request towards the Clash core!
|
||||||
|
But before so, please do the following checklist:
|
||||||
|
|
||||||
|
- [ ] I have searched on the [issue tracker](……/) before creating the issue.
|
||||||
|
|
||||||
|
Please understand that we close issues that fail to follow the issue template.
|
||||||
|
-->
|
||||||
|
|
||||||
|
我都确认过了,我要继续提交。
|
||||||
|
<!-- None of the above, create a feature request -->
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
|
||||||
|
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
|
||||||
|
|
||||||
|
### Clash core config
|
||||||
|
<!--
|
||||||
|
在下方附上 Clash Core 脱敏后的配置内容
|
||||||
|
Paste the Clash core configuration below.
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
……
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clash log
|
||||||
|
<!--
|
||||||
|
在下方附上 Clash Core 的日志,log level 请使用 DEBUG
|
||||||
|
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
……
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境 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
|
||||||
|
|
||||||
|
<!--
|
||||||
|
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||||
|
-->
|
||||||
|
|
||||||
|
### 可能的解决方案 Possible Solution
|
||||||
|
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
|
||||||
|
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
|
<!-- or ideas how to implement the addition or change -->
|
||||||
|
|
||||||
|
### 更多信息 More Information
|
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.13.x
|
go-version: 1.14.x
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
FROM golang:alpine as builder
|
FROM golang:alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make git && \
|
RUN apk add --no-cache make git && \
|
||||||
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
|
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
|
||||||
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
|
|
||||||
mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
|
|
||||||
WORKDIR /clash-src
|
WORKDIR /clash-src
|
||||||
COPY . /clash-src
|
COPY . /clash-src
|
||||||
RUN go mod download && \
|
RUN go mod download && \
|
||||||
|
20
Dockerfile.arm32v7
Normal file
20
Dockerfile.arm32v7
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM golang:alpine as builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache make git && \
|
||||||
|
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \
|
||||||
|
wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static && \
|
||||||
|
chmod +x /qemu-arm-static
|
||||||
|
|
||||||
|
WORKDIR /clash-src
|
||||||
|
COPY . /clash-src
|
||||||
|
RUN go mod download && \
|
||||||
|
make linux-armv7 && \
|
||||||
|
mv ./bin/clash-linux-armv7 /clash
|
||||||
|
|
||||||
|
FROM arm32v7/alpine:latest
|
||||||
|
|
||||||
|
COPY --from=builder /qemu-arm-static /usr/bin/
|
||||||
|
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||||
|
COPY --from=builder /clash /
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
ENTRYPOINT ["/clash"]
|
20
Dockerfile.arm64v8
Normal file
20
Dockerfile.arm64v8
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM golang:alpine as builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache make git && \
|
||||||
|
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \
|
||||||
|
wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static && \
|
||||||
|
chmod +x /qemu-aarch64-static
|
||||||
|
|
||||||
|
WORKDIR /clash-src
|
||||||
|
COPY . /clash-src
|
||||||
|
RUN go mod download && \
|
||||||
|
make linux-armv8 && \
|
||||||
|
mv ./bin/clash-linux-armv8 /clash
|
||||||
|
|
||||||
|
FROM arm64v8/alpine:latest
|
||||||
|
|
||||||
|
COPY --from=builder /qemu-aarch64-static /usr/bin/
|
||||||
|
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||||
|
COPY --from=builder /clash /
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
ENTRYPOINT ["/clash"]
|
114
README.md
114
README.md
@ -19,21 +19,25 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Local HTTP/HTTPS/SOCKS server
|
- Local HTTP/HTTPS/SOCKS server with/without authentication
|
||||||
- GeoIP rule support
|
- VMess, Shadowsocks, Trojan (experimental), Snell protocol support for remote connections. UDP is supported.
|
||||||
- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol
|
- Built-in DNS server that aims to minimize DNS pollution attacks, supports DoH/DoT upstream. Fake IP is also supported.
|
||||||
- Supports Netfilter TCP redirecting
|
- Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes
|
||||||
- Comprehensive HTTP API
|
- 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. You can deploy Clash on your Internet gateway with `iptables`.
|
||||||
|
- Comprehensive HTTP API controller
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Clash Requires Go >= 1.13. You can build it from source:
|
Clash requires Go >= 1.13. You can build it from source:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u -v github.com/Dreamacro/clash
|
$ go get -u -v github.com/Dreamacro/clash
|
||||||
```
|
```
|
||||||
|
|
||||||
Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases)
|
Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases)
|
||||||
|
Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available.
|
||||||
|
|
||||||
Check Clash version with:
|
Check Clash version with:
|
||||||
|
|
||||||
@ -41,21 +45,17 @@ Check Clash version with:
|
|||||||
$ clash -v
|
$ clash -v
|
||||||
```
|
```
|
||||||
|
|
||||||
## Daemon
|
## Daemonize Clash
|
||||||
|
|
||||||
Unfortunately, there is no native and elegant way to implement daemons on Golang.
|
Unfortunately, there is no native or elegant way to implement daemons on Golang. We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service.
|
||||||
|
|
||||||
So we can use third-party daemon tools like PM2, Supervisor or the like.
|
In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way:
|
||||||
|
|
||||||
In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ pm2 start clash
|
$ pm2 start clash
|
||||||
```
|
```
|
||||||
|
|
||||||
If you have Docker installed, you can run clash directly using `docker-compose`.
|
If you have Docker installed, it's recommended to deploy Clash directly using `docker-compose`: [run Clash in Docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
|
||||||
|
|
||||||
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
|
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ allow-lan: false
|
|||||||
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
||||||
# bind-address: "*"
|
# bind-address: "*"
|
||||||
|
|
||||||
# Rule / Global/ Direct (default is Rule)
|
# Rule / Global / Direct (default is Rule)
|
||||||
mode: Rule
|
mode: Rule
|
||||||
|
|
||||||
# set log level to stdout (default is info)
|
# set log level to stdout (default is info)
|
||||||
@ -112,6 +112,7 @@ external-controller: 127.0.0.1:9090
|
|||||||
# experimental feature
|
# experimental feature
|
||||||
experimental:
|
experimental:
|
||||||
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
|
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
|
||||||
|
# interface-name: en0 # outbound interface name
|
||||||
|
|
||||||
# authentication of local SOCKS5/HTTP(S) server
|
# authentication of local SOCKS5/HTTP(S) server
|
||||||
# authentication:
|
# authentication:
|
||||||
@ -119,19 +120,23 @@ experimental:
|
|||||||
# - "user2:pass2"
|
# - "user2:pass2"
|
||||||
|
|
||||||
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
|
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
|
||||||
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com)
|
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com)
|
||||||
# hosts:
|
# hosts:
|
||||||
# '*.clash.dev': 127.0.0.1
|
# '*.clash.dev': 127.0.0.1
|
||||||
|
# '.dev': 127.0.0.1
|
||||||
# 'alpha.clash.dev': '::1'
|
# 'alpha.clash.dev': '::1'
|
||||||
|
|
||||||
# dns:
|
# dns:
|
||||||
# enable: true # set true to enable dns (default is false)
|
# enable: true # set true to enable dns (default is false)
|
||||||
# ipv6: false # default is false
|
# ipv6: false # default is false
|
||||||
# listen: 0.0.0.0:53
|
# listen: 0.0.0.0:53
|
||||||
|
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
|
||||||
|
# # - 114.114.114.114
|
||||||
|
# # - 8.8.8.8
|
||||||
# enhanced-mode: redir-host # or fake-ip
|
# enhanced-mode: redir-host # or fake-ip
|
||||||
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
|
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
|
||||||
# fake-ip-filter: # fake ip white domain list
|
# fake-ip-filter: # fake ip white domain list
|
||||||
# - *.lan
|
# - '*.lan'
|
||||||
# - localhost.ptlogin2.qq.com
|
# - localhost.ptlogin2.qq.com
|
||||||
# nameserver:
|
# nameserver:
|
||||||
# - 114.114.114.114
|
# - 114.114.114.114
|
||||||
@ -144,7 +149,7 @@ experimental:
|
|||||||
# ipcidr: # ips in these subnets will be considered polluted
|
# ipcidr: # ips in these subnets will be considered polluted
|
||||||
# - 240.0.0.0/4
|
# - 240.0.0.0/4
|
||||||
|
|
||||||
Proxy:
|
proxies:
|
||||||
# shadowsocks
|
# shadowsocks
|
||||||
# The supported ciphers(encrypt methods):
|
# The supported ciphers(encrypt methods):
|
||||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||||
@ -206,6 +211,24 @@ Proxy:
|
|||||||
# ws-headers:
|
# ws-headers:
|
||||||
# Host: v2ray.com
|
# Host: v2ray.com
|
||||||
|
|
||||||
|
- name: "vmess-http"
|
||||||
|
type: vmess
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
alterId: 32
|
||||||
|
cipher: auto
|
||||||
|
# udp: true
|
||||||
|
# network: http
|
||||||
|
# http-opts:
|
||||||
|
# # method: "GET"
|
||||||
|
# # path:
|
||||||
|
# # - '/'
|
||||||
|
# # - '/video'
|
||||||
|
# # headers:
|
||||||
|
# # Connection:
|
||||||
|
# # - keep-alive
|
||||||
|
|
||||||
# socks5
|
# socks5
|
||||||
- name: "socks"
|
- name: "socks"
|
||||||
type: socks5
|
type: socks5
|
||||||
@ -237,7 +260,30 @@ Proxy:
|
|||||||
# mode: http # or tls
|
# mode: http # or tls
|
||||||
# host: bing.com
|
# host: bing.com
|
||||||
|
|
||||||
Proxy Group:
|
# trojan
|
||||||
|
- name: "trojan"
|
||||||
|
type: trojan
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
password: yourpsk
|
||||||
|
# udp: true
|
||||||
|
# sni: example.com # aka server name
|
||||||
|
# alpn:
|
||||||
|
# - h2
|
||||||
|
# - http/1.1
|
||||||
|
# skip-cert-verify: true
|
||||||
|
|
||||||
|
proxy-groups:
|
||||||
|
# relay chains the proxies. proxies shall not contain a relay. No UDP support.
|
||||||
|
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||||
|
- name: "relay"
|
||||||
|
type: relay
|
||||||
|
proxies:
|
||||||
|
- http
|
||||||
|
- vmess
|
||||||
|
- ss1
|
||||||
|
- ss2
|
||||||
|
|
||||||
# url-test select which proxy will be used by benchmarking speed to a URL.
|
# url-test select which proxy will be used by benchmarking speed to a URL.
|
||||||
- name: "auto"
|
- name: "auto"
|
||||||
type: url-test
|
type: url-test
|
||||||
@ -278,7 +324,33 @@ Proxy Group:
|
|||||||
- vmess1
|
- vmess1
|
||||||
- auto
|
- auto
|
||||||
|
|
||||||
Rule:
|
- name: UseProvider
|
||||||
|
type: select
|
||||||
|
use:
|
||||||
|
- provider1
|
||||||
|
proxies:
|
||||||
|
- Proxy
|
||||||
|
- DIRECT
|
||||||
|
|
||||||
|
proxy-providers:
|
||||||
|
provider1:
|
||||||
|
type: http
|
||||||
|
url: "url"
|
||||||
|
interval: 3600
|
||||||
|
path: ./hk.yaml
|
||||||
|
health-check:
|
||||||
|
enable: true
|
||||||
|
interval: 600
|
||||||
|
url: http://www.gstatic.com/generate_204
|
||||||
|
test:
|
||||||
|
type: file
|
||||||
|
path: /test.yaml
|
||||||
|
health-check:
|
||||||
|
enable: true
|
||||||
|
interval: 36000
|
||||||
|
url: http://www.gstatic.com/generate_204
|
||||||
|
|
||||||
|
rules:
|
||||||
- DOMAIN-SUFFIX,google.com,auto
|
- DOMAIN-SUFFIX,google.com,auto
|
||||||
- DOMAIN-KEYWORD,google,auto
|
- DOMAIN-KEYWORD,google,auto
|
||||||
- DOMAIN,google.com,auto
|
- DOMAIN,google.com,auto
|
||||||
@ -302,7 +374,7 @@ Rule:
|
|||||||
## Documentations
|
## Documentations
|
||||||
https://clash.gitbook.io/
|
https://clash.gitbook.io/
|
||||||
|
|
||||||
## Thanks
|
## Credits
|
||||||
|
|
||||||
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||||
|
|
||||||
|
@ -17,9 +17,9 @@ func (s *PacketAdapter) Metadata() *C.Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPacket is PacketAdapter generator
|
// NewPacket is PacketAdapter generator
|
||||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter {
|
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = netType
|
metadata.NetWork = C.UDP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
|
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
|
@ -19,9 +19,9 @@ func (s *SocketAdapter) Metadata() *C.Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSocket is SocketAdapter generator
|
// NewSocket is SocketAdapter generator
|
||||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, netType C.NetWork) *SocketAdapter {
|
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = netType
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||||
metadata.SrcIP = ip
|
metadata.SrcIP = ip
|
||||||
|
@ -18,6 +18,7 @@ var (
|
|||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
name string
|
name string
|
||||||
|
addr string
|
||||||
tp C.AdapterType
|
tp C.AdapterType
|
||||||
udp bool
|
udp bool
|
||||||
}
|
}
|
||||||
@ -30,8 +31,12 @@ func (b *Base) Type() C.AdapterType {
|
|||||||
return b.tp
|
return b.tp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
return nil, nil, errors.New("no support")
|
return c, errors.New("no support")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
return nil, errors.New("no support")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) SupportUDP() bool {
|
func (b *Base) SupportUDP() bool {
|
||||||
@ -44,8 +49,16 @@ func (b *Base) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBase(name string, tp C.AdapterType, udp bool) *Base {
|
func (b *Base) Addr() string {
|
||||||
return &Base{name, tp, udp}
|
return b.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
type conn struct {
|
||||||
@ -61,12 +74,17 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
|||||||
c.chain = append(c.chain, a.Name())
|
c.chain = append(c.chain, a.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||||
return &conn{c, []string{a.Name()}}
|
return &conn{c, []string{a.Name()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
type packetConn struct {
|
type PacketConn interface {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConn struct {
|
||||||
|
PacketConn
|
||||||
chain C.Chain
|
chain C.Chain
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +96,8 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
|||||||
c.chain = append(c.chain, a.Name())
|
c.chain = append(c.chain, a.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPacketConn(c net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||||
return &packetConn{c, []string{a.Name()}}
|
return &packetConn{pc, []string{a.Name()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
@ -189,7 +207,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
|||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := http.Client{Transport: transport}
|
client := http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,30 +14,37 @@ type Direct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
address := net.JoinHostPort(metadata.Host, metadata.DstPort)
|
address := net.JoinHostPort(metadata.String(), metadata.DstPort)
|
||||||
if metadata.DstIP != nil {
|
|
||||||
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := dialContext(ctx, "tcp", address)
|
c, err := dialer.DialContext(ctx, "tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return newConn(c, d), nil
|
return NewConn(c, d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, err := net.ListenPacket("udp", "")
|
pc, err := dialer.ListenPacket("udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return newPacketConn(&directPacketConn{pc}, d), nil
|
||||||
|
}
|
||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", metadata.RemoteAddress())
|
type directPacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||||
|
if !metadata.Resolved() {
|
||||||
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return newPacketConn(pc, d), addr, nil
|
metadata.DstIP = ip
|
||||||
|
}
|
||||||
|
return dp.WriteTo(p, metadata.UDPAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDirect() *Direct {
|
func NewDirect() *Direct {
|
||||||
|
@ -13,12 +13,12 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Http struct {
|
type Http struct {
|
||||||
*Base
|
*Base
|
||||||
addr string
|
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
@ -34,23 +34,35 @@ type HttpOption struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialContext(ctx, "tcp", h.addr)
|
if h.tlsConfig != nil {
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
cc := tls.Client(c, h.tlsConfig)
|
cc := tls.Client(c, h.tlsConfig)
|
||||||
err = cc.Handshake()
|
err := cc.Handshake()
|
||||||
c = cc
|
c = cc
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := h.shakeHand(metadata, c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
if err := h.shakeHand(metadata, c); err != nil {
|
|
||||||
|
c, err = h.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newConn(c, h), nil
|
return NewConn(c, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||||
@ -112,9 +124,9 @@ func NewHttp(option HttpOption) *Http {
|
|||||||
return &Http{
|
return &Http{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Http,
|
tp: C.Http,
|
||||||
},
|
},
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
|
@ -39,7 +39,12 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
}
|
}
|
||||||
proxy = NewHttp(*httpOption)
|
proxy = NewHttp(*httpOption)
|
||||||
case "vmess":
|
case "vmess":
|
||||||
vmessOption := &VmessOption{}
|
vmessOption := &VmessOption{
|
||||||
|
HTTPOpts: HTTPOptions{
|
||||||
|
Method: "GET",
|
||||||
|
Path: []string{"/"},
|
||||||
|
},
|
||||||
|
}
|
||||||
err = decoder.Decode(mapping, vmessOption)
|
err = decoder.Decode(mapping, vmessOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
@ -52,6 +57,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = NewSnell(*snellOption)
|
proxy, err = NewSnell(*snellOption)
|
||||||
|
case "trojan":
|
||||||
|
trojanOption := &TrojanOption{}
|
||||||
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
proxy, err = NewTrojan(*trojanOption)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@ -14,7 +15,11 @@ type Reject struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
return newConn(&NopConn{}, r), nil
|
return NewConn(&NopConn{}, r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
return nil, errors.New("match reject rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReject() *Reject {
|
func NewReject() *Reject {
|
||||||
@ -22,6 +27,7 @@ func NewReject() *Reject {
|
|||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: "REJECT",
|
name: "REJECT",
|
||||||
tp: C.Reject,
|
tp: C.Reject,
|
||||||
|
udp: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
|
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
|
||||||
@ -19,7 +20,6 @@ import (
|
|||||||
|
|
||||||
type ShadowSocks struct {
|
type ShadowSocks struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
cipher core.Cipher
|
cipher core.Cipher
|
||||||
|
|
||||||
// obfs
|
// obfs
|
||||||
@ -58,48 +58,49 @@ type v2rayObfsOption struct {
|
|||||||
Mux bool `obfs:"mux,omitempty"`
|
Mux bool `obfs:"mux,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialContext(ctx, "tcp", ss.server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
||||||
case "http":
|
case "http":
|
||||||
_, port, _ := net.SplitHostPort(ss.server)
|
_, port, _ := net.SplitHostPort(ss.addr)
|
||||||
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
||||||
case "websocket":
|
case "websocket":
|
||||||
var err error
|
var err error
|
||||||
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
|
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c = ss.cipher.StreamConn(c)
|
c = ss.cipher.StreamConn(c)
|
||||||
_, err = c.Write(serializesSocksAddr(metadata))
|
_, err := c.Write(serializesSocksAddr(metadata))
|
||||||
return newConn(c, ss), err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
pc, err := net.ListenPacket("udp", "")
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
c, err = ss.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, ss), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
pc, err := dialer.ListenPacket("udp", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := resolveUDPAddr("udp", ss.server)
|
addr, err := resolveUDPAddr("udp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
|
|
||||||
if targetAddr == nil {
|
|
||||||
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pc = ss.cipher.PacketConn(pc)
|
pc = ss.cipher.PacketConn(pc)
|
||||||
return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil
|
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
||||||
@ -109,12 +110,12 @@ func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
cipher := option.Cipher
|
cipher := option.Cipher
|
||||||
password := option.Password
|
password := option.Password
|
||||||
ciph, err := core.PickCipher(cipher, nil, password)
|
ciph, err := core.PickCipher(cipher, nil, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize error: %w", server, err)
|
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayOption *v2rayObfs.Option
|
var v2rayOption *v2rayObfs.Option
|
||||||
@ -136,49 +137,45 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
if option.Plugin == "obfs" {
|
if option.Plugin == "obfs" {
|
||||||
opts := simpleObfsOption{Host: "bing.com"}
|
opts := simpleObfsOption{Host: "bing.com"}
|
||||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err)
|
return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Mode != "tls" && opts.Mode != "http" {
|
if opts.Mode != "tls" && opts.Mode != "http" {
|
||||||
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
|
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
|
||||||
}
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
obfsOption = &opts
|
obfsOption = &opts
|
||||||
} else if option.Plugin == "v2ray-plugin" {
|
} else if option.Plugin == "v2ray-plugin" {
|
||||||
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
|
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
|
||||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err)
|
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Mode != "websocket" {
|
if opts.Mode != "websocket" {
|
||||||
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
|
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
|
||||||
}
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
if opts.TLS {
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
ServerName: opts.Host,
|
|
||||||
InsecureSkipVerify: opts.SkipCertVerify,
|
|
||||||
ClientSessionCache: getClientSessionCache(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v2rayOption = &v2rayObfs.Option{
|
v2rayOption = &v2rayObfs.Option{
|
||||||
Host: opts.Host,
|
Host: opts.Host,
|
||||||
Path: opts.Path,
|
Path: opts.Path,
|
||||||
Headers: opts.Headers,
|
Headers: opts.Headers,
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
Mux: opts.Mux,
|
Mux: opts.Mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.TLS {
|
||||||
|
v2rayOption.TLS = true
|
||||||
|
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||||
|
v2rayOption.SessionCache = getClientSessionCache()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
tp: C.Shadowsocks,
|
tp: C.Shadowsocks,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
server: server,
|
|
||||||
cipher: ciph,
|
cipher: ciph,
|
||||||
|
|
||||||
obfsMode: obfsMode,
|
obfsMode: obfsMode,
|
||||||
@ -187,27 +184,43 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ssUDPConn struct {
|
type ssPacketConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
rAddr socks5.Addr
|
rAddr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return uc.PacketConn.WriteTo(packet[3:], addr)
|
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||||
n, _, e := uc.PacketConn.ReadFrom(b)
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
|
||||||
addr := socks5.SplitAddr(b[:n])
|
if err != nil {
|
||||||
var from net.Addr
|
return
|
||||||
if e == nil {
|
|
||||||
// Get the source IP/Port of packet.
|
|
||||||
from = addr.UDPAddr()
|
|
||||||
}
|
}
|
||||||
copy(b, b[len(addr):])
|
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
|
||||||
return n - len(addr), from, e
|
}
|
||||||
|
|
||||||
|
func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, _, e := spc.PacketConn.ReadFrom(b)
|
||||||
|
if e != nil {
|
||||||
|
return 0, nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := socks5.SplitAddr(b[:n])
|
||||||
|
if addr == nil {
|
||||||
|
return 0, nil, errors.New("parse addr error")
|
||||||
|
}
|
||||||
|
|
||||||
|
udpAddr := addr.UDPAddr()
|
||||||
|
if udpAddr == nil {
|
||||||
|
return 0, nil, errors.New("parse addr error")
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(b, b[len(addr):])
|
||||||
|
return n - len(addr), udpAddr, e
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||||
"github.com/Dreamacro/clash/component/snell"
|
"github.com/Dreamacro/clash/component/snell"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -14,7 +15,6 @@ import (
|
|||||||
|
|
||||||
type Snell struct {
|
type Snell struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
psk []byte
|
psk []byte
|
||||||
obfsOption *simpleObfsOption
|
obfsOption *simpleObfsOption
|
||||||
}
|
}
|
||||||
@ -27,45 +27,51 @@ type SnellOption struct {
|
|||||||
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialContext(ctx, "tcp", s.server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
|
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
switch s.obfsOption.Mode {
|
switch s.obfsOption.Mode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
||||||
case "http":
|
case "http":
|
||||||
_, port, _ := net.SplitHostPort(s.server)
|
_, port, _ := net.SplitHostPort(s.addr)
|
||||||
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
||||||
}
|
}
|
||||||
c = snell.StreamConn(c, s.psk)
|
c = snell.StreamConn(c, s.psk)
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
err = snell.WriteHeader(c, metadata.String(), uint(port))
|
err := snell.WriteHeader(c, metadata.String(), uint(port))
|
||||||
return newConn(c, s), err
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
c, err = s.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSnell(option SnellOption) (*Snell, error) {
|
func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
psk := []byte(option.Psk)
|
psk := []byte(option.Psk)
|
||||||
|
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||||
obfsOption := &simpleObfsOption{Host: "bing.com"}
|
obfsOption := &simpleObfsOption{Host: "bing.com"}
|
||||||
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
|
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
|
||||||
return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err)
|
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
|
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
|
||||||
return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode)
|
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Snell{
|
return &Snell{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
tp: C.Snell,
|
tp: C.Snell,
|
||||||
},
|
},
|
||||||
server: server,
|
|
||||||
psk: psk,
|
psk: psk,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -3,19 +3,20 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Socks5 struct {
|
type Socks5 struct {
|
||||||
*Base
|
*Base
|
||||||
addr string
|
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
tls bool
|
tls bool
|
||||||
@ -34,19 +35,16 @@ type Socks5Option struct {
|
|||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := dialContext(ctx, "tcp", ss.addr)
|
if ss.tls {
|
||||||
|
|
||||||
if err == nil && ss.tls {
|
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
err = cc.Handshake()
|
err := cc.Handshake()
|
||||||
c = cc
|
c = cc
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
}
|
||||||
|
|
||||||
var user *socks5.User
|
var user *socks5.User
|
||||||
if ss.user != "" {
|
if ss.user != "" {
|
||||||
user = &socks5.User{
|
user = &socks5.User{
|
||||||
@ -57,13 +55,28 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
|||||||
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newConn(c, ss), nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) {
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
c, err = ss.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, ss), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
c, err := dialContext(ctx, "tcp", ss.addr)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
return
|
return
|
||||||
@ -96,17 +109,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", bindAddr.String())
|
pc, err := dialer.ListenPacket("udp", "")
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
|
|
||||||
if targetAddr == nil {
|
|
||||||
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err := net.ListenPacket("udp", "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -119,7 +122,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
|
|||||||
pc.Close()
|
pc.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, nil
|
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(option Socks5Option) *Socks5 {
|
func NewSocks5(option Socks5Option) *Socks5 {
|
||||||
@ -135,10 +138,10 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
|||||||
return &Socks5{
|
return &Socks5{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Socks5,
|
tp: C.Socks5,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
},
|
},
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
user: option.UserName,
|
user: option.UserName,
|
||||||
pass: option.Password,
|
pass: option.Password,
|
||||||
tls: option.TLS,
|
tls: option.TLS,
|
||||||
@ -147,22 +150,30 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type socksUDPConn struct {
|
type socksPacketConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
rAddr socks5.Addr
|
rAddr net.Addr
|
||||||
tcpConn net.Conn
|
tcpConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return uc.PacketConn.WriteTo(packet, addr)
|
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||||
n, a, e := uc.PacketConn.ReadFrom(b)
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, _, e := uc.PacketConn.ReadFrom(b)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return 0, nil, e
|
return 0, nil, e
|
||||||
}
|
}
|
||||||
@ -170,13 +181,18 @@ func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
udpAddr := addr.UDPAddr()
|
||||||
|
if udpAddr == nil {
|
||||||
|
return 0, nil, errors.New("parse udp addr error")
|
||||||
|
}
|
||||||
|
|
||||||
// due to DecodeUDPPacket is mutable, record addr length
|
// due to DecodeUDPPacket is mutable, record addr length
|
||||||
addrLength := len(addr)
|
|
||||||
copy(b, payload)
|
copy(b, payload)
|
||||||
return n - addrLength - 3, a, nil
|
return n - len(addr) - 3, udpAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *socksUDPConn) Close() error {
|
func (uc *socksPacketConn) Close() error {
|
||||||
uc.tcpConn.Close()
|
uc.tcpConn.Close()
|
||||||
return uc.PacketConn.Close()
|
return uc.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
116
adapters/outbound/trojan.go
Normal file
116
adapters/outbound/trojan.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/trojan"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trojan struct {
|
||||||
|
*Base
|
||||||
|
instance *trojan.Trojan
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrojanOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Server string `proxy:"server"`
|
||||||
|
Port int `proxy:"port"`
|
||||||
|
Password string `proxy:"password"`
|
||||||
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
|
SNI string `proxy:"sni,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
c, err := t.instance.StreamConn(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c, err = t.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, t), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||||
|
defer cancel()
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c, err = t.instance.StreamConn(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := t.instance.PacketConn(c)
|
||||||
|
return newPacketConn(&trojanPacketConn{pc, c}, 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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.SNI != "" {
|
||||||
|
tOption.ServerName = option.SNI
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Trojan{
|
||||||
|
Base: &Base{
|
||||||
|
name: option.Name,
|
||||||
|
addr: addr,
|
||||||
|
tp: C.Trojan,
|
||||||
|
udp: option.UDP,
|
||||||
|
},
|
||||||
|
instance: trojan.New(tOption),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type trojanPacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||||
|
return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p)
|
||||||
|
}
|
@ -2,7 +2,6 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -11,9 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -87,92 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
|
|||||||
return bytes.Join(buf, nil)
|
return bytes.Join(buf, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
returned := make(chan struct{})
|
|
||||||
defer close(returned)
|
|
||||||
|
|
||||||
type dialResult struct {
|
|
||||||
net.Conn
|
|
||||||
error
|
|
||||||
resolved bool
|
|
||||||
ipv6 bool
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
results := make(chan dialResult)
|
|
||||||
var primary, fallback dialResult
|
|
||||||
|
|
||||||
startRacer := func(ctx context.Context, host string, ipv6 bool) {
|
|
||||||
dialer := net.Dialer{}
|
|
||||||
result := dialResult{ipv6: ipv6, done: true}
|
|
||||||
defer func() {
|
|
||||||
select {
|
|
||||||
case results <- result:
|
|
||||||
case <-returned:
|
|
||||||
if result.Conn != nil {
|
|
||||||
result.Conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var ip net.IP
|
|
||||||
if ipv6 {
|
|
||||||
ip, result.error = dns.ResolveIPv6(host)
|
|
||||||
} else {
|
|
||||||
ip, result.error = dns.ResolveIPv4(host)
|
|
||||||
}
|
|
||||||
if result.error != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result.resolved = true
|
|
||||||
|
|
||||||
if ipv6 {
|
|
||||||
result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port))
|
|
||||||
} else {
|
|
||||||
result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go startRacer(ctx, host, false)
|
|
||||||
go startRacer(ctx, host, true)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case res := <-results:
|
|
||||||
if res.error == nil {
|
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !res.ipv6 {
|
|
||||||
primary = res
|
|
||||||
} else {
|
|
||||||
fallback = res
|
|
||||||
}
|
|
||||||
|
|
||||||
if primary.done && fallback.done {
|
|
||||||
if primary.resolved {
|
|
||||||
return nil, primary.error
|
|
||||||
} else if fallback.resolved {
|
|
||||||
return nil, fallback.error
|
|
||||||
} else {
|
|
||||||
return nil, primary.error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := dns.ResolveIP(host)
|
ip, err := resolver.ResolveIP(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,23 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/component/vmess"
|
"github.com/Dreamacro/clash/component/vmess"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Vmess struct {
|
type Vmess struct {
|
||||||
*Base
|
*Base
|
||||||
server string
|
|
||||||
client *vmess.Client
|
client *vmess.Client
|
||||||
|
option *VmessOption
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
@ -27,34 +31,106 @@ type VmessOption struct {
|
|||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
type HTTPOptions struct {
|
||||||
c, err := dialContext(ctx, "tcp", v.server)
|
Method string `proxy:"method,omitempty"`
|
||||||
if err != nil {
|
Path []string `proxy:"path,omitempty"`
|
||||||
return nil, fmt.Errorf("%s connect error", v.server)
|
Headers map[string][]string `proxy:"headers,omitempty"`
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
c, err = v.client.New(c, parseVmessAddr(metadata))
|
|
||||||
return newConn(c, v), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
var err error
|
||||||
defer cancel()
|
switch v.option.Network {
|
||||||
c, err := dialContext(ctx, "tcp", v.server)
|
case "ws":
|
||||||
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Path: v.option.WSPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.option.WSHeaders) != 0 {
|
||||||
|
header := http.Header{}
|
||||||
|
for key, value := range v.option.WSHeaders {
|
||||||
|
header.Add(key, value)
|
||||||
|
}
|
||||||
|
wsOpts.Headers = header
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.TLS {
|
||||||
|
wsOpts.TLS = true
|
||||||
|
wsOpts.SessionCache = getClientSessionCache()
|
||||||
|
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||||
|
}
|
||||||
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
|
case "http":
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
httpOpts := &vmess.HTTPConfig{
|
||||||
|
Host: host,
|
||||||
|
Method: v.option.HTTPOpts.Method,
|
||||||
|
Path: v.option.HTTPOpts.Path,
|
||||||
|
Headers: v.option.HTTPOpts.Headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||||
|
default:
|
||||||
|
// handle TLS
|
||||||
|
if v.option.TLS {
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
tlsOpts := &vmess.TLSConfig{
|
||||||
|
Host: host,
|
||||||
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
|
SessionCache: getClientSessionCache(),
|
||||||
|
}
|
||||||
|
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("%s connect error", v.server)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error", v.addr)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = v.client.New(c, parseVmessAddr(metadata))
|
|
||||||
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
|
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
|
||||||
|
if !metadata.Resolved() {
|
||||||
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("new vmess client error: %v", err)
|
return nil, errors.New("can't resolve ip")
|
||||||
}
|
}
|
||||||
return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), nil
|
metadata.DstIP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||||
|
defer cancel()
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error", v.addr)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||||
|
}
|
||||||
|
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
@ -63,14 +139,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
UUID: option.UUID,
|
UUID: option.UUID,
|
||||||
AlterID: uint16(option.AlterID),
|
AlterID: uint16(option.AlterID),
|
||||||
Security: security,
|
Security: security,
|
||||||
TLS: option.TLS,
|
|
||||||
HostName: option.Server,
|
HostName: option.Server,
|
||||||
Port: strconv.Itoa(option.Port),
|
Port: strconv.Itoa(option.Port),
|
||||||
NetWork: option.Network,
|
|
||||||
WebSocketPath: option.WSPath,
|
|
||||||
WebSocketHeaders: option.WSHeaders,
|
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
|
||||||
SessionCache: getClientSessionCache(),
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -79,11 +149,12 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
return &Vmess{
|
return &Vmess{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Vmess,
|
tp: C.Vmess,
|
||||||
udp: true,
|
udp: true,
|
||||||
},
|
},
|
||||||
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
||||||
client: client,
|
client: client,
|
||||||
|
option: &option,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,15 +186,20 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type vmessUDPConn struct {
|
type vmessPacketConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
|
rAddr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
return uc.Conn.Write(b)
|
return uc.Conn.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||||
n, err := uc.Conn.Read(b)
|
return uc.Conn.Write(p)
|
||||||
return n, uc.RemoteAddr(), err
|
}
|
||||||
|
|
||||||
|
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, err := uc.Conn.Read(b)
|
||||||
|
return n, uc.rAddr, err
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
@ -31,13 +30,13 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
proxy := f.findAliveProxy()
|
proxy := f.findAliveProxy()
|
||||||
pc, addr, err := proxy.DialUDP(metadata)
|
pc, err := proxy.DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(f)
|
pc.AppendToChains(f)
|
||||||
}
|
}
|
||||||
return pc, addr, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fallback) SupportUDP() bool {
|
func (f *Fallback) SupportUDP() bool {
|
||||||
@ -57,6 +56,11 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
proxy := f.findAliveProxy()
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fallback) proxies() []C.Proxy {
|
func (f *Fallback) proxies() []C.Proxy {
|
||||||
elm, _, _ := f.single.Do(func() (interface{}, error) {
|
elm, _, _ := f.single.Do(func() (interface{}, error) {
|
||||||
return getProvidersProxies(f.providers), nil
|
return getProvidersProxies(f.providers), nil
|
||||||
@ -78,7 +82,7 @@ func (f *Fallback) findAliveProxy() C.Proxy {
|
|||||||
|
|
||||||
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
|
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
|
||||||
return &Fallback{
|
return &Fallback{
|
||||||
Base: outbound.NewBase(name, C.Fallback, false),
|
Base: outbound.NewBase(name, "", C.Fallback, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
}
|
}
|
||||||
|
@ -59,28 +59,29 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
proxy := lb.Unwrap(metadata)
|
||||||
proxies := lb.proxies()
|
|
||||||
buckets := int32(len(proxies))
|
|
||||||
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
|
|
||||||
idx := jumpHash(key, buckets)
|
|
||||||
proxy := proxies[idx]
|
|
||||||
if proxy.Alive() {
|
|
||||||
c, err = proxy.DialContext(ctx, metadata)
|
c, err = proxy.DialContext(ctx, metadata)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
|
||||||
c, err = proxies[0].DialContext(ctx, metadata)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) {
|
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(lb)
|
pc.AppendToChains(lb)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
proxy := lb.Unwrap(metadata)
|
||||||
|
|
||||||
|
return proxy.DialUDP(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) SupportUDP() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||||
proxies := lb.proxies()
|
proxies := lb.proxies()
|
||||||
buckets := int32(len(proxies))
|
buckets := int32(len(proxies))
|
||||||
@ -88,15 +89,11 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.
|
|||||||
idx := jumpHash(key, buckets)
|
idx := jumpHash(key, buckets)
|
||||||
proxy := proxies[idx]
|
proxy := proxies[idx]
|
||||||
if proxy.Alive() {
|
if proxy.Alive() {
|
||||||
return proxy.DialUDP(metadata)
|
return proxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxies[0].DialUDP(metadata)
|
return proxies[0]
|
||||||
}
|
|
||||||
|
|
||||||
func (lb *LoadBalance) SupportUDP() bool {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lb *LoadBalance) proxies() []C.Proxy {
|
func (lb *LoadBalance) proxies() []C.Proxy {
|
||||||
@ -120,7 +117,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
|
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
|
||||||
return &LoadBalance{
|
return &LoadBalance{
|
||||||
Base: outbound.NewBase(name, C.LoadBalance, false),
|
Base: outbound.NewBase(name, "", C.LoadBalance, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
maxRetry: 3,
|
maxRetry: 3,
|
||||||
providers: providers,
|
providers: providers,
|
||||||
|
@ -56,7 +56,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
// if Use not empty, drop health check options
|
// if Use not empty, drop health check options
|
||||||
if len(groupOption.Use) != 0 {
|
if len(groupOption.Use) != 0 {
|
||||||
hc := provider.NewHealthCheck(ps, "", 0)
|
hc := provider.NewHealthCheck(ps, "", 0)
|
||||||
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -64,9 +64,9 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
providers = append(providers, pd)
|
providers = append(providers, pd)
|
||||||
} else {
|
} else {
|
||||||
// select don't need health check
|
// select don't need health check
|
||||||
if groupOption.Type == "select" {
|
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||||
hc := provider.NewHealthCheck(ps, "", 0)
|
hc := provider.NewHealthCheck(ps, "", 0)
|
||||||
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
}
|
}
|
||||||
|
|
||||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
|
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
|
||||||
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -108,6 +108,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
group = NewFallback(groupName, providers)
|
group = NewFallback(groupName, providers)
|
||||||
case "load-balance":
|
case "load-balance":
|
||||||
group = NewLoadBalance(groupName, providers)
|
group = NewLoadBalance(groupName, providers)
|
||||||
|
case "relay":
|
||||||
|
group = NewRelay(groupName, providers)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
|
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
|
||||||
}
|
}
|
||||||
|
98
adapters/outboundgroup/relay.go
Normal file
98
adapters/outboundgroup/relay.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package outboundgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Relay struct {
|
||||||
|
*outbound.Base
|
||||||
|
single *singledo.Single
|
||||||
|
providers []provider.ProxyProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
|
proxies := r.proxies(metadata)
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return nil, errors.New("Proxy does not exist")
|
||||||
|
}
|
||||||
|
first := proxies[0]
|
||||||
|
last := proxies[len(proxies)-1]
|
||||||
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", first.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
var currentMeta *C.Metadata
|
||||||
|
for _, proxy := range proxies[1:] {
|
||||||
|
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = first.StreamConn(c, currentMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
first = proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = last.StreamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outbound.NewConn(c, r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||||
|
var all []string
|
||||||
|
for _, proxy := range r.rawProxies() {
|
||||||
|
all = append(all, proxy.Name())
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"type": r.Type().String(),
|
||||||
|
"all": all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) rawProxies() []C.Proxy {
|
||||||
|
elm, _, _ := r.single.Do(func() (interface{}, error) {
|
||||||
|
return getProvidersProxies(r.providers), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return elm.([]C.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
|
||||||
|
proxies := r.rawProxies()
|
||||||
|
|
||||||
|
for n, proxy := range proxies {
|
||||||
|
subproxy := proxy.Unwrap(metadata)
|
||||||
|
for subproxy != nil {
|
||||||
|
proxies[n] = subproxy
|
||||||
|
subproxy = subproxy.Unwrap(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRelay(name string, providers []provider.ProxyProvider) *Relay {
|
||||||
|
return &Relay{
|
||||||
|
Base: outbound.NewBase(name, "", C.Relay, false),
|
||||||
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
|
providers: providers,
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
@ -15,33 +14,33 @@ import (
|
|||||||
type Selector struct {
|
type Selector struct {
|
||||||
*outbound.Base
|
*outbound.Base
|
||||||
single *singledo.Single
|
single *singledo.Single
|
||||||
selected C.Proxy
|
selected string
|
||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
c, err := s.selected.DialContext(ctx, metadata)
|
c, err := s.selectedProxy().DialContext(ctx, metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(s)
|
c.AppendToChains(s)
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, addr, err := s.selected.DialUDP(metadata)
|
pc, err := s.selectedProxy().DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(s)
|
pc.AppendToChains(s)
|
||||||
}
|
}
|
||||||
return pc, addr, err
|
return pc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) SupportUDP() bool {
|
func (s *Selector) SupportUDP() bool {
|
||||||
return s.selected.SupportUDP()
|
return s.selectedProxy().SupportUDP()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
for _, proxy := range s.proxies() {
|
for _, proxy := range getProvidersProxies(s.providers) {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,13 +52,14 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) Now() string {
|
func (s *Selector) Now() string {
|
||||||
return s.selected.Name()
|
return s.selectedProxy().Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) Set(name string) error {
|
func (s *Selector) Set(name string) error {
|
||||||
for _, proxy := range s.proxies() {
|
for _, proxy := range getProvidersProxies(s.providers) {
|
||||||
if proxy.Name() == name {
|
if proxy.Name() == name {
|
||||||
s.selected = proxy
|
s.selected = name
|
||||||
|
s.single.Reset()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,18 +67,29 @@ func (s *Selector) Set(name string) error {
|
|||||||
return errors.New("Proxy does not exist")
|
return errors.New("Proxy does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) proxies() []C.Proxy {
|
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
return s.selectedProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Selector) selectedProxy() C.Proxy {
|
||||||
elm, _, _ := s.single.Do(func() (interface{}, error) {
|
elm, _, _ := s.single.Do(func() (interface{}, error) {
|
||||||
return getProvidersProxies(s.providers), nil
|
proxies := getProvidersProxies(s.providers)
|
||||||
|
for _, proxy := range proxies {
|
||||||
|
if proxy.Name() == s.selected {
|
||||||
|
return proxy, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies[0], nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return elm.([]C.Proxy)
|
return elm.(C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
|
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
|
||||||
selected := providers[0].Proxies()[0]
|
selected := providers[0].Proxies()[0].Name()
|
||||||
return &Selector{
|
return &Selector{
|
||||||
Base: outbound.NewBase(name, C.Selector, false),
|
Base: outbound.NewBase(name, "", C.Selector, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
|
@ -3,7 +3,6 @@ package outboundgroup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
@ -31,12 +30,16 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
|||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||||
pc, addr, err := u.fast().DialUDP(metadata)
|
pc, err := u.fast().DialUDP(metadata)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pc.AppendToChains(u)
|
pc.AppendToChains(u)
|
||||||
}
|
}
|
||||||
return pc, addr, err
|
return pc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
return u.fast()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) proxies() []C.Proxy {
|
func (u *URLTest) proxies() []C.Proxy {
|
||||||
@ -87,7 +90,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
|
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
|
||||||
return &URLTest{
|
return &URLTest{
|
||||||
Base: outbound.NewBase(name, C.URLTest, false),
|
Base: outbound.NewBase(name, "", C.URLTest, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
fastSingle: singledo.NewSingle(time.Second * 10),
|
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
|
53
adapters/outboundgroup/util.go
Normal file
53
adapters/outboundgroup/util.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package outboundgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||||
|
host, port, err := net.SplitHostPort(rawAddress)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("addrToMetadata failed: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip != nil {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv4,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypIPv6,
|
||||||
|
Host: "",
|
||||||
|
DstIP: ip,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr = &C.Metadata{
|
||||||
|
AddrType: C.AtypDomainName,
|
||||||
|
Host: host,
|
||||||
|
DstIP: nil,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcpKeepAlive(c net.Conn) {
|
||||||
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
|
tcp.SetKeepAlive(true)
|
||||||
|
tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
154
adapters/provider/fetcher.go
Normal file
154
adapters/provider/fetcher.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileMode os.FileMode = 0666
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser = func([]byte) (interface{}, error)
|
||||||
|
|
||||||
|
type fetcher struct {
|
||||||
|
name string
|
||||||
|
vehicle Vehicle
|
||||||
|
updatedAt *time.Time
|
||||||
|
ticker *time.Ticker
|
||||||
|
hash [16]byte
|
||||||
|
parser parser
|
||||||
|
onUpdate func(interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fetcher) Name() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fetcher) VehicleType() VehicleType {
|
||||||
|
return f.vehicle.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fetcher) Initial() (interface{}, error) {
|
||||||
|
var buf []byte
|
||||||
|
var err error
|
||||||
|
var isLocal bool
|
||||||
|
if stat, err := os.Stat(f.vehicle.Path()); err == nil {
|
||||||
|
buf, err = ioutil.ReadFile(f.vehicle.Path())
|
||||||
|
modTime := stat.ModTime()
|
||||||
|
f.updatedAt = &modTime
|
||||||
|
isLocal = true
|
||||||
|
} else {
|
||||||
|
buf, err = f.vehicle.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies, err := f.parser(buf)
|
||||||
|
if err != nil {
|
||||||
|
if !isLocal {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse local file error, fallback to remote
|
||||||
|
buf, err = f.vehicle.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies, err = f.parser(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.hash = md5.Sum(buf)
|
||||||
|
|
||||||
|
// pull proxies automatically
|
||||||
|
if f.ticker != nil {
|
||||||
|
go f.pullLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fetcher) Update() (interface{}, bool, error) {
|
||||||
|
buf, err := f.vehicle.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
hash := md5.Sum(buf)
|
||||||
|
if bytes.Equal(f.hash[:], hash[:]) {
|
||||||
|
f.updatedAt = &now
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies, err := f.parser(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.updatedAt = &now
|
||||||
|
f.hash = hash
|
||||||
|
|
||||||
|
return proxies, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fetcher) Destroy() error {
|
||||||
|
if f.ticker != nil {
|
||||||
|
f.ticker.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fetcher) pullLoop() {
|
||||||
|
for range f.ticker.C {
|
||||||
|
elm, same, err := f.Update()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if same {
|
||||||
|
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("[Provider] %s's proxies update", f.Name())
|
||||||
|
if f.onUpdate != nil {
|
||||||
|
f.onUpdate(elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
|
||||||
|
var ticker *time.Ticker
|
||||||
|
if interval != 0 {
|
||||||
|
ticker = time.NewTicker(interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fetcher{
|
||||||
|
name: name,
|
||||||
|
ticker: ticker,
|
||||||
|
vehicle: vehicle,
|
||||||
|
parser: parser,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
|||||||
}
|
}
|
||||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
|
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
|
||||||
|
|
||||||
path := C.Path.Reslove(schema.Path)
|
path := C.Path.Resolve(schema.Path)
|
||||||
|
|
||||||
var vehicle Vehicle
|
var vehicle Vehicle
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"runtime"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ReservedName = "default"
|
ReservedName = "default"
|
||||||
|
|
||||||
fileMode = 0666
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider Type
|
// Provider Type
|
||||||
@ -49,8 +43,7 @@ type Provider interface {
|
|||||||
VehicleType() VehicleType
|
VehicleType() VehicleType
|
||||||
Type() ProviderType
|
Type() ProviderType
|
||||||
Initial() error
|
Initial() error
|
||||||
Reload() error
|
Update() error
|
||||||
Destroy() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyProvider interface
|
// ProxyProvider interface
|
||||||
@ -58,24 +51,24 @@ type ProxyProvider interface {
|
|||||||
Provider
|
Provider
|
||||||
Proxies() []C.Proxy
|
Proxies() []C.Proxy
|
||||||
HealthCheck()
|
HealthCheck()
|
||||||
Update() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxySchema struct {
|
type ProxySchema struct {
|
||||||
Proxies []map[string]interface{} `yaml:"proxies"`
|
Proxies []map[string]interface{} `yaml:"proxies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for auto gc
|
||||||
type ProxySetProvider struct {
|
type ProxySetProvider struct {
|
||||||
name string
|
*proxySetProvider
|
||||||
vehicle Vehicle
|
|
||||||
hash [16]byte
|
|
||||||
proxies []C.Proxy
|
|
||||||
healthCheck *HealthCheck
|
|
||||||
ticker *time.Ticker
|
|
||||||
updatedAt *time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
|
type proxySetProvider struct {
|
||||||
|
*fetcher
|
||||||
|
proxies []C.Proxy
|
||||||
|
healthCheck *HealthCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"name": pp.Name(),
|
"name": pp.Name(),
|
||||||
"type": pp.Type().String(),
|
"type": pp.Type().String(),
|
||||||
@ -85,119 +78,41 @@ func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Name() string {
|
func (pp *proxySetProvider) Name() string {
|
||||||
return pp.name
|
return pp.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Reload() error {
|
func (pp *proxySetProvider) HealthCheck() {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *ProxySetProvider) HealthCheck() {
|
|
||||||
pp.healthCheck.check()
|
pp.healthCheck.check()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Update() error {
|
func (pp *proxySetProvider) Update() error {
|
||||||
return pp.pull()
|
elm, same, err := pp.fetcher.Update()
|
||||||
|
if err == nil && !same {
|
||||||
|
pp.onUpdate(elm)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Destroy() error {
|
func (pp *proxySetProvider) Initial() error {
|
||||||
pp.healthCheck.close()
|
elm, err := pp.fetcher.Initial()
|
||||||
|
|
||||||
if pp.ticker != nil {
|
|
||||||
pp.ticker.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Initial() error {
|
|
||||||
var buf []byte
|
|
||||||
var err error
|
|
||||||
if stat, err := os.Stat(pp.vehicle.Path()); err == nil {
|
|
||||||
buf, err = ioutil.ReadFile(pp.vehicle.Path())
|
|
||||||
modTime := stat.ModTime()
|
|
||||||
pp.updatedAt = &modTime
|
|
||||||
} else {
|
|
||||||
buf, err = pp.vehicle.Read()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies, err := pp.parse(buf)
|
pp.onUpdate(elm)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pp.hash = md5.Sum(buf)
|
|
||||||
pp.setProxies(proxies)
|
|
||||||
|
|
||||||
// pull proxies automatically
|
|
||||||
if pp.ticker != nil {
|
|
||||||
go pp.pullLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) VehicleType() VehicleType {
|
func (pp *proxySetProvider) Type() ProviderType {
|
||||||
return pp.vehicle.Type()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Type() ProviderType {
|
|
||||||
return Proxy
|
return Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Proxies() []C.Proxy {
|
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||||
return pp.proxies
|
return pp.proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) pullLoop() {
|
func proxiesParse(buf []byte) (interface{}, error) {
|
||||||
for range pp.ticker.C {
|
|
||||||
if err := pp.pull(); err != nil {
|
|
||||||
log.Warnln("[Provider] %s pull error: %s", pp.Name(), err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *ProxySetProvider) pull() error {
|
|
||||||
buf, err := pp.vehicle.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
hash := md5.Sum(buf)
|
|
||||||
if bytes.Equal(pp.hash[:], hash[:]) {
|
|
||||||
log.Debugln("[Provider] %s's proxies doesn't change", pp.Name())
|
|
||||||
pp.updatedAt = &now
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
proxies, err := pp.parse(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infoln("[Provider] %s's proxies update", pp.Name())
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pp.updatedAt = &now
|
|
||||||
pp.hash = hash
|
|
||||||
pp.setProxies(proxies)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
|
|
||||||
schema := &ProxySchema{}
|
schema := &ProxySchema{}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||||
@ -224,38 +139,52 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
|
|||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) {
|
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||||
pp.proxies = proxies
|
pp.proxies = proxies
|
||||||
pp.healthCheck.setProxy(proxies)
|
pp.healthCheck.setProxy(proxies)
|
||||||
go pp.healthCheck.check()
|
go pp.healthCheck.check()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
|
func stopProxyProvider(pd *ProxySetProvider) {
|
||||||
var ticker *time.Ticker
|
pd.healthCheck.close()
|
||||||
if interval != 0 {
|
pd.fetcher.Destroy()
|
||||||
ticker = time.NewTicker(interval)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
|
||||||
if hc.auto() {
|
if hc.auto() {
|
||||||
go hc.process()
|
go hc.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProxySetProvider{
|
pd := &proxySetProvider{
|
||||||
name: name,
|
|
||||||
vehicle: vehicle,
|
|
||||||
proxies: []C.Proxy{},
|
proxies: []C.Proxy{},
|
||||||
healthCheck: hc,
|
healthCheck: hc,
|
||||||
ticker: ticker,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUpdate := func(elm interface{}) {
|
||||||
|
ret := elm.([]C.Proxy)
|
||||||
|
pd.setProxies(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher := newFetcher(name, interval, vehicle, proxiesParse, onUpdate)
|
||||||
|
pd.fetcher = fetcher
|
||||||
|
|
||||||
|
wrapper := &ProxySetProvider{pd}
|
||||||
|
runtime.SetFinalizer(wrapper, stopProxyProvider)
|
||||||
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompatibleProvier struct {
|
// for auto gc
|
||||||
|
type CompatibleProvider struct {
|
||||||
|
*compatibleProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
type compatibleProvider struct {
|
||||||
name string
|
name string
|
||||||
healthCheck *HealthCheck
|
healthCheck *HealthCheck
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
|
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"name": cp.Name(),
|
"name": cp.Name(),
|
||||||
"type": cp.Type().String(),
|
"type": cp.Type().String(),
|
||||||
@ -264,44 +193,39 @@ func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Name() string {
|
func (cp *compatibleProvider) Name() string {
|
||||||
return cp.name
|
return cp.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Reload() error {
|
func (cp *compatibleProvider) HealthCheck() {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Destroy() error {
|
|
||||||
cp.healthCheck.close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *CompatibleProvier) HealthCheck() {
|
|
||||||
cp.healthCheck.check()
|
cp.healthCheck.check()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Update() error {
|
func (cp *compatibleProvider) Update() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Initial() error {
|
func (cp *compatibleProvider) Initial() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) VehicleType() VehicleType {
|
func (cp *compatibleProvider) VehicleType() VehicleType {
|
||||||
return Compatible
|
return Compatible
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Type() ProviderType {
|
func (cp *compatibleProvider) Type() ProviderType {
|
||||||
return Proxy
|
return Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CompatibleProvier) Proxies() []C.Proxy {
|
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||||
return cp.proxies
|
return cp.proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) {
|
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||||
|
pd.healthCheck.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return nil, errors.New("Provider need one proxy at least")
|
return nil, errors.New("Provider need one proxy at least")
|
||||||
}
|
}
|
||||||
@ -310,9 +234,13 @@ func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*Com
|
|||||||
go hc.process()
|
go hc.process()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CompatibleProvier{
|
pd := &compatibleProvider{
|
||||||
name: name,
|
name: name,
|
||||||
proxies: proxies,
|
proxies: proxies,
|
||||||
healthCheck: hc,
|
healthCheck: hc,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
wrapper := &CompatibleProvider{pd}
|
||||||
|
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
|
||||||
|
return wrapper, nil
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Vehicle Type
|
// Vehicle Type
|
||||||
@ -73,10 +76,21 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, h.url, nil)
|
uri, err := url.Parse(h.url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user := uri.User; user != nil {
|
||||||
|
password, _ := user.Password()
|
||||||
|
req.SetBasicAuth(user.Username(), password)
|
||||||
|
}
|
||||||
|
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
@ -85,6 +99,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := http.Client{Transport: transport}
|
client := http.Client{Transport: transport}
|
||||||
@ -92,15 +107,12 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
buf, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(h.path, buf, fileMode); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
common/cache/lrucache.go
vendored
85
common/cache/lrucache.go
vendored
@ -12,7 +12,7 @@ import (
|
|||||||
type Option func(*LruCache)
|
type Option func(*LruCache)
|
||||||
|
|
||||||
// EvictCallback is used to get a callback when a cache entry is evicted
|
// EvictCallback is used to get a callback when a cache entry is evicted
|
||||||
type EvictCallback func(key interface{}, value interface{})
|
type EvictCallback = func(key interface{}, value interface{})
|
||||||
|
|
||||||
// WithEvict set the evict callback
|
// WithEvict set the evict callback
|
||||||
func WithEvict(cb EvictCallback) Option {
|
func WithEvict(cb EvictCallback) Option {
|
||||||
@ -42,6 +42,14 @@ func WithSize(maxSize int) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStale decide whether Stale return is enabled.
|
||||||
|
// If this feature is enabled, element will not get Evicted according to `WithAge`.
|
||||||
|
func WithStale(stale bool) Option {
|
||||||
|
return func(l *LruCache) {
|
||||||
|
l.staleReturn = stale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LruCache is a thread-safe, in-memory lru-cache that evicts the
|
// LruCache is a thread-safe, in-memory lru-cache that evicts the
|
||||||
// least recently used entries from memory when (if set) the entries are
|
// least recently used entries from memory when (if set) the entries are
|
||||||
// older than maxAge (in seconds). Use the New constructor to create one.
|
// older than maxAge (in seconds). Use the New constructor to create one.
|
||||||
@ -52,6 +60,7 @@ type LruCache struct {
|
|||||||
cache map[interface{}]*list.Element
|
cache map[interface{}]*list.Element
|
||||||
lru *list.List // Front is least-recent
|
lru *list.List // Front is least-recent
|
||||||
updateAgeOnGet bool
|
updateAgeOnGet bool
|
||||||
|
staleReturn bool
|
||||||
onEvict EvictCallback
|
onEvict EvictCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,31 +81,28 @@ func NewLRUCache(options ...Option) *LruCache {
|
|||||||
// Get returns the interface{} representation of a cached response and a bool
|
// Get returns the interface{} representation of a cached response and a bool
|
||||||
// set to true if the key was found.
|
// set to true if the key was found.
|
||||||
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
|
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
|
||||||
c.mu.Lock()
|
entry := c.get(key)
|
||||||
defer c.mu.Unlock()
|
if entry == nil {
|
||||||
|
|
||||||
le, ok := c.cache[key]
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
|
|
||||||
c.deleteElement(le)
|
|
||||||
c.maybeDeleteOldest()
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lru.MoveToBack(le)
|
|
||||||
entry := le.Value.(*entry)
|
|
||||||
if c.maxAge > 0 && c.updateAgeOnGet {
|
|
||||||
entry.expires = time.Now().Unix() + c.maxAge
|
|
||||||
}
|
|
||||||
value := entry.value
|
value := entry.value
|
||||||
|
|
||||||
return value, true
|
return value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWithExpire returns the interface{} representation of a cached response,
|
||||||
|
// a time.Time Give expected expires,
|
||||||
|
// and a bool set to true if the key was found.
|
||||||
|
// This method will NOT check the maxAge of element and will NOT update the expires.
|
||||||
|
func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool) {
|
||||||
|
entry := c.get(key)
|
||||||
|
if entry == nil {
|
||||||
|
return nil, time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.value, time.Unix(entry.expires, 0), true
|
||||||
|
}
|
||||||
|
|
||||||
// Exist returns if key exist in cache but not put item to the head of linked list
|
// Exist returns if key exist in cache but not put item to the head of linked list
|
||||||
func (c *LruCache) Exist(key interface{}) bool {
|
func (c *LruCache) Exist(key interface{}) bool {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@ -108,21 +114,26 @@ func (c *LruCache) Exist(key interface{}) bool {
|
|||||||
|
|
||||||
// Set stores the interface{} representation of a response for a given key.
|
// Set stores the interface{} representation of a response for a given key.
|
||||||
func (c *LruCache) Set(key interface{}, value interface{}) {
|
func (c *LruCache) Set(key interface{}, value interface{}) {
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
expires := int64(0)
|
expires := int64(0)
|
||||||
if c.maxAge > 0 {
|
if c.maxAge > 0 {
|
||||||
expires = time.Now().Unix() + c.maxAge
|
expires = time.Now().Unix() + c.maxAge
|
||||||
}
|
}
|
||||||
|
c.SetWithExpire(key, value, time.Unix(expires, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWithExpire stores the interface{} representation of a response for a given key and given exires.
|
||||||
|
// The expires time will round to second.
|
||||||
|
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if le, ok := c.cache[key]; ok {
|
if le, ok := c.cache[key]; ok {
|
||||||
c.lru.MoveToBack(le)
|
c.lru.MoveToBack(le)
|
||||||
e := le.Value.(*entry)
|
e := le.Value.(*entry)
|
||||||
e.value = value
|
e.value = value
|
||||||
e.expires = expires
|
e.expires = expires.Unix()
|
||||||
} else {
|
} else {
|
||||||
e := &entry{key: key, value: value, expires: expires}
|
e := &entry{key: key, value: value, expires: expires.Unix()}
|
||||||
c.cache[key] = c.lru.PushBack(e)
|
c.cache[key] = c.lru.PushBack(e)
|
||||||
|
|
||||||
if c.maxSize > 0 {
|
if c.maxSize > 0 {
|
||||||
@ -135,6 +146,30 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
|
|||||||
c.maybeDeleteOldest()
|
c.maybeDeleteOldest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *LruCache) get(key interface{}) *entry {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
le, ok := c.cache[key]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
|
||||||
|
c.deleteElement(le)
|
||||||
|
c.maybeDeleteOldest()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lru.MoveToBack(le)
|
||||||
|
entry := le.Value.(*entry)
|
||||||
|
if c.maxAge > 0 && c.updateAgeOnGet {
|
||||||
|
entry.expires = time.Now().Unix() + c.maxAge
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes the value associated with a key.
|
// Delete removes the value associated with a key.
|
||||||
func (c *LruCache) Delete(key string) {
|
func (c *LruCache) Delete(key string) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@ -147,7 +182,7 @@ func (c *LruCache) Delete(key string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *LruCache) maybeDeleteOldest() {
|
func (c *LruCache) maybeDeleteOldest() {
|
||||||
if c.maxAge > 0 {
|
if !c.staleReturn && c.maxAge > 0 {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
|
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
|
||||||
c.deleteElement(le)
|
c.deleteElement(le)
|
||||||
|
28
common/cache/lrucache_test.go
vendored
28
common/cache/lrucache_test.go
vendored
@ -136,3 +136,31 @@ func TestEvict(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, temp, 3)
|
assert.Equal(t, temp, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetWithExpire(t *testing.T) {
|
||||||
|
c := NewLRUCache(WithAge(1))
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
tenSecBefore := time.Unix(now-10, 0)
|
||||||
|
c.SetWithExpire(1, 2, tenSecBefore)
|
||||||
|
|
||||||
|
// res is expected not to exist, and expires should be empty time.Time
|
||||||
|
res, expires, exist := c.GetWithExpire(1)
|
||||||
|
assert.Equal(t, nil, res)
|
||||||
|
assert.Equal(t, time.Time{}, expires)
|
||||||
|
assert.Equal(t, false, exist)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStale(t *testing.T) {
|
||||||
|
c := NewLRUCache(WithAge(1), WithStale(true))
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
tenSecBefore := time.Unix(now-10, 0)
|
||||||
|
c.SetWithExpire(1, 2, tenSecBefore)
|
||||||
|
|
||||||
|
res, expires, exist := c.GetWithExpire(1)
|
||||||
|
assert.Equal(t, 2, res)
|
||||||
|
assert.Equal(t, tenSecBefore, expires)
|
||||||
|
assert.Equal(t, true, exist)
|
||||||
|
}
|
||||||
|
@ -16,16 +16,15 @@ type Picker struct {
|
|||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
|
errOnce sync.Once
|
||||||
result interface{}
|
result interface{}
|
||||||
|
err error
|
||||||
firstDone chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPicker(ctx context.Context, cancel func()) *Picker {
|
func newPicker(ctx context.Context, cancel func()) *Picker {
|
||||||
return &Picker{
|
return &Picker{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
firstDone: make(chan struct{}, 1),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +41,6 @@ func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.C
|
|||||||
return newPicker(ctx, cancel), ctx
|
return newPicker(ctx, cancel), ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithoutAutoCancel returns a new Picker and an associated Context derived from ctx,
|
|
||||||
// but it wouldn't cancel context when the first element return.
|
|
||||||
func WithoutAutoCancel(ctx context.Context) *Picker {
|
|
||||||
return newPicker(ctx, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait blocks until all function calls from the Go method have returned,
|
// Wait blocks until all function calls from the Go method have returned,
|
||||||
// then returns the first nil error result (if any) from them.
|
// then returns the first nil error result (if any) from them.
|
||||||
func (p *Picker) Wait() interface{} {
|
func (p *Picker) Wait() interface{} {
|
||||||
@ -58,14 +51,9 @@ func (p *Picker) Wait() interface{} {
|
|||||||
return p.result
|
return p.result
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitWithoutCancel blocks until the first result return, if timeout will return nil.
|
// Error return the first error (if all success return nil)
|
||||||
func (p *Picker) WaitWithoutCancel() interface{} {
|
func (p *Picker) Error() error {
|
||||||
select {
|
return p.err
|
||||||
case <-p.firstDone:
|
|
||||||
return p.result
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
return p.result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go calls the given function in a new goroutine.
|
// Go calls the given function in a new goroutine.
|
||||||
@ -79,11 +67,14 @@ func (p *Picker) Go(f func() (interface{}, error)) {
|
|||||||
if ret, err := f(); err == nil {
|
if ret, err := f(); err == nil {
|
||||||
p.once.Do(func() {
|
p.once.Do(func() {
|
||||||
p.result = ret
|
p.result = ret
|
||||||
p.firstDone <- struct{}{}
|
|
||||||
if p.cancel != nil {
|
if p.cancel != nil {
|
||||||
p.cancel()
|
p.cancel()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
p.errOnce.Do(func() {
|
||||||
|
p.err = err
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -36,31 +36,5 @@ func TestPicker_Timeout(t *testing.T) {
|
|||||||
|
|
||||||
number := picker.Wait()
|
number := picker.Wait()
|
||||||
assert.Nil(t, number)
|
assert.Nil(t, number)
|
||||||
}
|
assert.NotNil(t, picker.Error())
|
||||||
|
|
||||||
func TestPicker_WaitWithoutAutoCancel(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*60)
|
|
||||||
defer cancel()
|
|
||||||
picker := WithoutAutoCancel(ctx)
|
|
||||||
|
|
||||||
trigger := false
|
|
||||||
picker.Go(sleepAndSend(ctx, 10, 1))
|
|
||||||
picker.Go(func() (interface{}, error) {
|
|
||||||
timer := time.NewTimer(time.Millisecond * time.Duration(30))
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
trigger = true
|
|
||||||
return 2, nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
elm := picker.WaitWithoutCancel()
|
|
||||||
|
|
||||||
assert.NotNil(t, elm)
|
|
||||||
assert.Equal(t, elm.(int), 1)
|
|
||||||
|
|
||||||
elm = picker.Wait()
|
|
||||||
assert.True(t, trigger)
|
|
||||||
assert.Equal(t, elm.(int), 1)
|
|
||||||
}
|
}
|
||||||
|
65
common/pool/alloc.go
Normal file
65
common/pool/alloc.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
// Inspired by https://github.com/xtaci/smux/blob/master/alloc.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultAllocator *Allocator
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
defaultAllocator = NewAllocator()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||||
|
type Allocator struct {
|
||||||
|
buffers []sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
|
||||||
|
// the waste(memory fragmentation) of space allocation is guaranteed to be
|
||||||
|
// no more than 50%.
|
||||||
|
func NewAllocator() *Allocator {
|
||||||
|
alloc := new(Allocator)
|
||||||
|
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
|
||||||
|
for k := range alloc.buffers {
|
||||||
|
i := k
|
||||||
|
alloc.buffers[k].New = func() interface{} {
|
||||||
|
return make([]byte, 1<<uint32(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return alloc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a []byte from pool with most appropriate cap
|
||||||
|
func (alloc *Allocator) Get(size int) []byte {
|
||||||
|
if size <= 0 || size > 65536 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bits := msb(size)
|
||||||
|
if size == 1<<bits {
|
||||||
|
return alloc.buffers[bits].Get().([]byte)[:size]
|
||||||
|
}
|
||||||
|
|
||||||
|
return alloc.buffers[bits+1].Get().([]byte)[:size]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put returns a []byte to pool for future use,
|
||||||
|
// which the cap must be exactly 2^n
|
||||||
|
func (alloc *Allocator) Put(buf []byte) error {
|
||||||
|
bits := msb(cap(buf))
|
||||||
|
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
||||||
|
return errors.New("allocator Put() incorrect buffer size")
|
||||||
|
}
|
||||||
|
alloc.buffers[bits].Put(buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// msb return the pos of most significiant bit
|
||||||
|
func msb(size int) uint16 {
|
||||||
|
return uint16(bits.Len32(uint32(size)) - 1)
|
||||||
|
}
|
48
common/pool/alloc_test.go
Normal file
48
common/pool/alloc_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAllocGet(t *testing.T) {
|
||||||
|
alloc := NewAllocator()
|
||||||
|
assert.Nil(t, alloc.Get(0))
|
||||||
|
assert.Equal(t, 1, len(alloc.Get(1)))
|
||||||
|
assert.Equal(t, 2, len(alloc.Get(2)))
|
||||||
|
assert.Equal(t, 3, len(alloc.Get(3)))
|
||||||
|
assert.Equal(t, 4, cap(alloc.Get(3)))
|
||||||
|
assert.Equal(t, 4, cap(alloc.Get(4)))
|
||||||
|
assert.Equal(t, 1023, len(alloc.Get(1023)))
|
||||||
|
assert.Equal(t, 1024, cap(alloc.Get(1023)))
|
||||||
|
assert.Equal(t, 1024, len(alloc.Get(1024)))
|
||||||
|
assert.Equal(t, 65536, len(alloc.Get(65536)))
|
||||||
|
assert.Nil(t, alloc.Get(65537))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocPut(t *testing.T) {
|
||||||
|
alloc := NewAllocator()
|
||||||
|
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||||
|
assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior")
|
||||||
|
assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior")
|
||||||
|
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
|
||||||
|
assert.Nil(t, alloc.Put(make([]byte, 65536, 65536)), "put elem:65536 []bytes misbehavior")
|
||||||
|
assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocPutThenGet(t *testing.T) {
|
||||||
|
alloc := NewAllocator()
|
||||||
|
data := alloc.Get(4)
|
||||||
|
alloc.Put(data)
|
||||||
|
newData := alloc.Get(4)
|
||||||
|
|
||||||
|
assert.Equal(t, cap(data), cap(newData), "different cap while alloc.Get()")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMSB(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
msb(rand.Int())
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,16 @@
|
|||||||
package pool
|
package pool
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// io.Copy default buffer size is 32 KiB
|
// io.Copy default buffer size is 32 KiB
|
||||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
// 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
|
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||||
bufferSize = 20 * 1024
|
RelayBufferSize = 20 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
// BufPool provide buffer for relay
|
func Get(size int) []byte {
|
||||||
var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
|
return defaultAllocator.Get(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(buf []byte) error {
|
||||||
|
return defaultAllocator.Put(buf)
|
||||||
|
}
|
||||||
|
@ -50,6 +50,10 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s
|
|||||||
return call.val, call.err, false
|
return call.val, call.err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Single) Reset() {
|
||||||
|
s.last = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
func NewSingle(wait time.Duration) *Single {
|
func NewSingle(wait time.Duration) *Single {
|
||||||
return &Single{wait: wait}
|
return &Single{wait: wait}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func TestBasic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
const n = 10
|
const n = 5
|
||||||
wg.Add(n)
|
wg.Add(n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
@ -33,7 +33,7 @@ func TestBasic(t *testing.T) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
assert.Equal(t, 1, foo)
|
assert.Equal(t, 1, foo)
|
||||||
assert.Equal(t, 9, shardCount)
|
assert.Equal(t, 4, shardCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimer(t *testing.T) {
|
func TestTimer(t *testing.T) {
|
||||||
@ -51,3 +51,18 @@ func TestTimer(t *testing.T) {
|
|||||||
assert.Equal(t, 1, foo)
|
assert.Equal(t, 1, foo)
|
||||||
assert.True(t, shard)
|
assert.True(t, shard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReset(t *testing.T) {
|
||||||
|
single := NewSingle(time.Millisecond * 30)
|
||||||
|
foo := 0
|
||||||
|
call := func() (interface{}, error) {
|
||||||
|
foo++
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
single.Do(call)
|
||||||
|
single.Reset()
|
||||||
|
single.Do(call)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, foo)
|
||||||
|
}
|
||||||
|
19
common/sockopt/reuseaddr_linux.go
Normal file
19
common/sockopt/reuseaddr_linux.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package sockopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UDPReuseaddr(c *net.UDPConn) (err error) {
|
||||||
|
rc, err := c.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.Control(func(fd uintptr) {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
11
common/sockopt/reuseaddr_other.go
Normal file
11
common/sockopt/reuseaddr_other.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package sockopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UDPReuseaddr(c *net.UDPConn) (err error) {
|
||||||
|
return
|
||||||
|
}
|
@ -216,6 +216,11 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
|
|||||||
}
|
}
|
||||||
|
|
||||||
v := dataVal.MapIndex(k).Interface()
|
v := dataVal.MapIndex(k).Interface()
|
||||||
|
if v == nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("filed %s invalid", fieldName))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
currentVal := reflect.Indirect(reflect.New(valElemType))
|
currentVal := reflect.Indirect(reflect.New(valElemType))
|
||||||
if err := d.decode(fieldName, v, currentVal); err != nil {
|
if err := d.decode(fieldName, v, currentVal); err != nil {
|
||||||
errors = append(errors, err.Error())
|
errors = append(errors, err.Error())
|
||||||
|
174
component/dialer/dialer.go
Normal file
174
component/dialer/dialer.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"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 ListenConfig() (*net.ListenConfig, error) {
|
||||||
|
cfg := &net.ListenConfig{}
|
||||||
|
if ListenConfigHook != nil {
|
||||||
|
if err := ListenConfigHook(cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, 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) {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "tcp6", "udp4", "udp6":
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer, err := Dialer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
ip, err = resolver.ResolveIPv4(host)
|
||||||
|
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))
|
||||||
|
case "tcp", "udp":
|
||||||
|
return dualStackDailContext(ctx, network, address)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("network invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||||
|
lc, err := ListenConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ListenPacketHook != nil && address == "" {
|
||||||
|
ip, err := ListenPacketHook()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
address = net.JoinHostPort(ip.String(), "0")
|
||||||
|
}
|
||||||
|
return lc.ListenPacket(context.Background(), network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dualStackDailContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
|
||||||
|
type dialResult struct {
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
resolved bool
|
||||||
|
ipv6 bool
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
results := make(chan dialResult)
|
||||||
|
var primary, fallback dialResult
|
||||||
|
|
||||||
|
startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
|
||||||
|
result := dialResult{ipv6: ipv6, done: true}
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case results <- result:
|
||||||
|
case <-returned:
|
||||||
|
if result.Conn != nil {
|
||||||
|
result.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
dialer, err := Dialer()
|
||||||
|
if err != nil {
|
||||||
|
result.error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
if ipv6 {
|
||||||
|
ip, result.error = resolver.ResolveIPv6(host)
|
||||||
|
} else {
|
||||||
|
ip, result.error = resolver.ResolveIPv4(host)
|
||||||
|
}
|
||||||
|
if result.error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
go startRacer(ctx, network+"4", host, false)
|
||||||
|
go startRacer(ctx, network+"6", host, true)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.ipv6 {
|
||||||
|
primary = res
|
||||||
|
} else {
|
||||||
|
fallback = res
|
||||||
|
}
|
||||||
|
|
||||||
|
if primary.done && fallback.done {
|
||||||
|
if primary.resolved {
|
||||||
|
return nil, primary.error
|
||||||
|
} else if fallback.resolved {
|
||||||
|
return nil, fallback.error
|
||||||
|
} else {
|
||||||
|
return nil, primary.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
component/dialer/hook.go
Normal file
148
component/dialer/hook.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DialerHookFunc = func(dialer *net.Dialer) error
|
||||||
|
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
|
||||||
|
type ListenConfigHookFunc = func(*net.ListenConfig) error
|
||||||
|
type ListenPacketHookFunc = func() (net.IP, error)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DialerHook DialerHookFunc
|
||||||
|
DialHook DialHookFunc
|
||||||
|
ListenConfigHook ListenConfigHookFunc
|
||||||
|
ListenPacketHook ListenPacketHookFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAddrNotFound = errors.New("addr not found")
|
||||||
|
ErrNetworkNotSupport = errors.New("network not support")
|
||||||
|
)
|
||||||
|
|
||||||
|
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 ListenPacketWithInterface(name string) ListenPacketHookFunc {
|
||||||
|
single := singledo.NewSingle(5 * time.Second)
|
||||||
|
|
||||||
|
return func() (net.IP, error) {
|
||||||
|
elm, err, _ := single.Do(func() (interface{}, error) {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs := elm.([]net.Addr)
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok || addr.IP.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr.IP, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrAddrNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialerWithInterface(name string) DialHookFunc {
|
||||||
|
single := singledo.NewSingle(5 * time.Second)
|
||||||
|
|
||||||
|
return func(dialer *net.Dialer, network string, ip net.IP) error {
|
||||||
|
elm, err, _ := single.Do(func() (interface{}, error) {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs := elm.([]net.Addr)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
wildcard = "*"
|
wildcard = "*"
|
||||||
|
dotWildcard = ""
|
||||||
domainStep = "."
|
domainStep = "."
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,8 +22,23 @@ type Trie struct {
|
|||||||
root *Node
|
root *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidDomain(domain string) bool {
|
func validAndSplitDomain(domain string) ([]string, bool) {
|
||||||
return domain != "" && domain[0] != '.' && domain[len(domain)-1] != '.'
|
if domain != "" && domain[len(domain)-1] == '.' {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(domain, domainStep)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range parts[1:] {
|
||||||
|
if part == "" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert adds a node to the trie.
|
// Insert adds a node to the trie.
|
||||||
@ -30,12 +46,13 @@ func isValidDomain(domain string) bool {
|
|||||||
// 1. www.example.com
|
// 1. www.example.com
|
||||||
// 2. *.example.com
|
// 2. *.example.com
|
||||||
// 3. subdomain.*.example.com
|
// 3. subdomain.*.example.com
|
||||||
|
// 4. .example.com
|
||||||
func (t *Trie) Insert(domain string, data interface{}) error {
|
func (t *Trie) Insert(domain string, data interface{}) error {
|
||||||
if !isValidDomain(domain) {
|
parts, valid := validAndSplitDomain(domain)
|
||||||
|
if !valid {
|
||||||
return ErrInvalidDomain
|
return ErrInvalidDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(domain, domainStep)
|
|
||||||
node := t.root
|
node := t.root
|
||||||
// reverse storage domain part to save space
|
// reverse storage domain part to save space
|
||||||
for i := len(parts) - 1; i >= 0; i-- {
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
@ -55,28 +72,45 @@ func (t *Trie) Insert(domain string, data interface{}) error {
|
|||||||
// Priority as:
|
// Priority as:
|
||||||
// 1. static part
|
// 1. static part
|
||||||
// 2. wildcard domain
|
// 2. wildcard domain
|
||||||
|
// 2. dot wildcard domain
|
||||||
func (t *Trie) Search(domain string) *Node {
|
func (t *Trie) Search(domain string) *Node {
|
||||||
if !isValidDomain(domain) {
|
parts, valid := validAndSplitDomain(domain)
|
||||||
|
if !valid || parts[0] == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
parts := strings.Split(domain, domainStep)
|
|
||||||
|
|
||||||
n := t.root
|
n := t.root
|
||||||
|
var dotWildcardNode *Node
|
||||||
|
var wildcardNode *Node
|
||||||
for i := len(parts) - 1; i >= 0; i-- {
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
part := parts[i]
|
part := parts[i]
|
||||||
|
|
||||||
var child *Node
|
if node := n.getChild(dotWildcard); node != nil {
|
||||||
if !n.hasChild(part) {
|
dotWildcardNode = node
|
||||||
if !n.hasChild(wildcard) {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
child = n.getChild(wildcard)
|
child := n.getChild(part)
|
||||||
} else {
|
if child == nil && wildcardNode != nil {
|
||||||
child = n.getChild(part)
|
child = wildcardNode.getChild(part)
|
||||||
}
|
}
|
||||||
|
wildcardNode = n.getChild(wildcard)
|
||||||
|
|
||||||
n = child
|
n = child
|
||||||
|
if n == nil {
|
||||||
|
n = wildcardNode
|
||||||
|
wildcardNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
if dotWildcardNode != nil {
|
||||||
|
return dotWildcardNode
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.Data == nil {
|
if n.Data == nil {
|
||||||
|
@ -3,6 +3,8 @@ package trie
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var localIP = net.IP{127, 0, 0, 1}
|
var localIP = net.IP{127, 0, 0, 1}
|
||||||
@ -19,17 +21,9 @@ func TestTrie_Basic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node := tree.Search("example.com")
|
node := tree.Search("example.com")
|
||||||
if node == nil {
|
assert.NotNil(t, node)
|
||||||
t.Error("should not recv nil")
|
assert.True(t, node.Data.(net.IP).Equal(localIP))
|
||||||
}
|
assert.NotNil(t, tree.Insert("", localIP))
|
||||||
|
|
||||||
if !node.Data.(net.IP).Equal(localIP) {
|
|
||||||
t.Error("should equal 127.0.0.1")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tree.Insert("", localIP) == nil {
|
|
||||||
t.Error("should return error")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrie_Wildcard(t *testing.T) {
|
func TestTrie_Wildcard(t *testing.T) {
|
||||||
@ -38,50 +32,56 @@ func TestTrie_Wildcard(t *testing.T) {
|
|||||||
"*.example.com",
|
"*.example.com",
|
||||||
"sub.*.example.com",
|
"sub.*.example.com",
|
||||||
"*.dev",
|
"*.dev",
|
||||||
|
".org",
|
||||||
|
".example.net",
|
||||||
|
".apple.*",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
tree.Insert(domain, localIP)
|
tree.Insert(domain, localIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tree.Search("sub.example.com") == nil {
|
assert.NotNil(t, tree.Search("sub.example.com"))
|
||||||
t.Error("should not recv nil")
|
assert.NotNil(t, tree.Search("sub.foo.example.com"))
|
||||||
|
assert.NotNil(t, tree.Search("test.org"))
|
||||||
|
assert.NotNil(t, tree.Search("test.example.net"))
|
||||||
|
assert.NotNil(t, tree.Search("test.apple.com"))
|
||||||
|
assert.Nil(t, tree.Search("foo.sub.example.com"))
|
||||||
|
assert.Nil(t, tree.Search("foo.example.dev"))
|
||||||
|
assert.Nil(t, tree.Search("example.com"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrie_Priority(t *testing.T) {
|
||||||
|
tree := New()
|
||||||
|
domains := []string{
|
||||||
|
".dev",
|
||||||
|
"example.dev",
|
||||||
|
"*.example.dev",
|
||||||
|
"test.example.dev",
|
||||||
}
|
}
|
||||||
|
|
||||||
if tree.Search("sub.foo.example.com") == nil {
|
assertFn := func(domain string, data int) {
|
||||||
t.Error("should not recv nil")
|
node := tree.Search(domain)
|
||||||
|
assert.NotNil(t, node)
|
||||||
|
assert.Equal(t, data, node.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tree.Search("foo.sub.example.com") != nil {
|
for idx, domain := range domains {
|
||||||
t.Error("should recv nil")
|
tree.Insert(domain, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tree.Search("foo.example.dev") != nil {
|
assertFn("test.dev", 0)
|
||||||
t.Error("should recv nil")
|
assertFn("foo.bar.dev", 0)
|
||||||
}
|
assertFn("example.dev", 1)
|
||||||
|
assertFn("foo.example.dev", 2)
|
||||||
if tree.Search("example.com") != nil {
|
assertFn("test.example.dev", 3)
|
||||||
t.Error("should recv nil")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrie_Boundary(t *testing.T) {
|
func TestTrie_Boundary(t *testing.T) {
|
||||||
tree := New()
|
tree := New()
|
||||||
tree.Insert("*.dev", localIP)
|
tree.Insert("*.dev", localIP)
|
||||||
|
|
||||||
if err := tree.Insert(".", localIP); err == nil {
|
assert.NotNil(t, tree.Insert(".", localIP))
|
||||||
t.Error("should recv err")
|
assert.NotNil(t, tree.Insert("..dev", localIP))
|
||||||
}
|
assert.Nil(t, tree.Search("dev"))
|
||||||
|
|
||||||
if err := tree.Insert(".com", localIP); err == nil {
|
|
||||||
t.Error("should recv err")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tree.Search("dev") != nil {
|
|
||||||
t.Error("should recv nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tree.Search(".dev") != nil {
|
|
||||||
t.Error("should recv nil")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
43
component/mmdb/mmdb.go
Normal file
43
component/mmdb/mmdb.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package mmdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
"github.com/oschwald/geoip2-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mmdb *geoip2.Reader
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
func LoadFromBytes(buffer []byte) {
|
||||||
|
once.Do(func() {
|
||||||
|
var err error
|
||||||
|
mmdb, err = geoip2.FromBytes(buffer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Can't load mmdb: %s", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Verify() bool {
|
||||||
|
instance, err := geoip2.Open(C.Path.MMDB())
|
||||||
|
if err == nil {
|
||||||
|
instance.Close()
|
||||||
|
}
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Instance() *geoip2.Reader {
|
||||||
|
once.Do(func() {
|
||||||
|
var err error
|
||||||
|
mmdb, err = geoip2.Open(C.Path.MMDB())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Can't load mmdb: %s", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return mmdb
|
||||||
|
}
|
@ -1,34 +1,25 @@
|
|||||||
package nat
|
package nat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Table struct {
|
type Table struct {
|
||||||
mapping sync.Map
|
mapping sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
type element struct {
|
func (t *Table) Set(key string, pc C.PacketConn) {
|
||||||
RemoteAddr net.Addr
|
t.mapping.Store(key, pc)
|
||||||
RemoteConn net.PacketConn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) Set(key string, pc net.PacketConn, addr net.Addr) {
|
func (t *Table) Get(key string) C.PacketConn {
|
||||||
// set conn read timeout
|
|
||||||
t.mapping.Store(key, &element{
|
|
||||||
RemoteConn: pc,
|
|
||||||
RemoteAddr: addr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Table) Get(key string) (net.PacketConn, net.Addr) {
|
|
||||||
item, exist := t.mapping.Load(key)
|
item, exist := t.mapping.Load(key)
|
||||||
if !exist {
|
if !exist {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
elm := item.(*element)
|
return item.(C.PacketConn)
|
||||||
return elm.RemoteConn, elm.RemoteAddr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {
|
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {
|
||||||
|
@ -1,16 +1,32 @@
|
|||||||
package dns
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errIPNotFound = errors.New("couldn't find ip")
|
// DefaultResolver aim to resolve ip
|
||||||
errIPVersion = errors.New("ip version error")
|
DefaultResolver Resolver
|
||||||
|
|
||||||
|
// DefaultHosts aim to resolve hosts
|
||||||
|
DefaultHosts = trie.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrIPNotFound = errors.New("couldn't find ip")
|
||||||
|
ErrIPVersion = errors.New("ip version error")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Resolver interface {
|
||||||
|
ResolveIP(host string) (ip net.IP, err error)
|
||||||
|
ResolveIPv4(host string) (ip net.IP, err error)
|
||||||
|
ResolveIPv6(host string) (ip net.IP, err error)
|
||||||
|
}
|
||||||
|
|
||||||
// ResolveIPv4 with a host, return ipv4
|
// ResolveIPv4 with a host, return ipv4
|
||||||
func ResolveIPv4(host string) (net.IP, error) {
|
func ResolveIPv4(host string) (net.IP, error) {
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
@ -24,7 +40,7 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||||||
if !strings.Contains(host, ":") {
|
if !strings.Contains(host, ":") {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
return nil, errIPVersion
|
return nil, ErrIPVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
@ -42,7 +58,7 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errIPNotFound
|
return nil, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv6 with a host, return ipv6
|
// ResolveIPv6 with a host, return ipv6
|
||||||
@ -58,7 +74,7 @@ func ResolveIPv6(host string) (net.IP, error) {
|
|||||||
if strings.Contains(host, ":") {
|
if strings.Contains(host, ":") {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
return nil, errIPVersion
|
return nil, ErrIPVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
@ -76,7 +92,7 @@ func ResolveIPv6(host string) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errIPNotFound
|
return nil, ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP with a host, return ip
|
// ResolveIP with a host, return ip
|
||||||
@ -86,11 +102,8 @@ func ResolveIP(host string) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
if DefaultResolver.ipv6 {
|
|
||||||
return DefaultResolver.ResolveIP(host)
|
return DefaultResolver.ResolveIP(host)
|
||||||
}
|
}
|
||||||
return DefaultResolver.ResolveIPv4(host)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(host)
|
ip := net.ParseIP(host)
|
||||||
if ip != nil {
|
if ip != nil {
|
@ -34,15 +34,15 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ho.firstResponse {
|
if ho.firstResponse {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
n, err := ho.Conn.Read(buf)
|
n, err := ho.Conn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
ho.firstResponse = false
|
ho.firstResponse = false
|
||||||
@ -52,7 +52,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
|||||||
ho.buf = buf[:idx+4+length]
|
ho.buf = buf[:idx+4+length]
|
||||||
ho.offset = idx + 4 + n
|
ho.offset = idx + 4 + n
|
||||||
} else {
|
} else {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@ type TLSObfs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
|
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(discardN)
|
||||||
_, err := io.ReadFull(to.Conn, buf[:discardN])
|
_, err := io.ReadFull(to.Conn, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
|
|
||||||
sizeBuf := make([]byte, 2)
|
sizeBuf := make([]byte, 2)
|
||||||
_, err = io.ReadFull(to.Conn, sizeBuf)
|
_, err = io.ReadFull(to.Conn, sizeBuf)
|
||||||
@ -102,15 +102,11 @@ func (to *TLSObfs) write(b []byte) (int, error) {
|
|||||||
return len(b), err
|
return len(b), err
|
||||||
}
|
}
|
||||||
|
|
||||||
size := pool.BufPool.Get().([]byte)
|
|
||||||
binary.BigEndian.PutUint16(size[:2], uint16(len(b)))
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||||
buf.Write(size[:2])
|
binary.Write(buf, binary.BigEndian, uint16(len(b)))
|
||||||
buf.Write(b)
|
buf.Write(b)
|
||||||
_, err := to.Conn.Write(buf.Bytes())
|
_, err := to.Conn.Write(buf.Bytes())
|
||||||
pool.BufPool.Put(size[:cap(size)])
|
|
||||||
return len(b), err
|
return len(b), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
command = buf[1]
|
command = buf[1]
|
||||||
addr, err = readAddr(rw, buf)
|
addr, err = ReadAddr(rw, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -260,10 +260,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return readAddr(rw, buf)
|
return ReadAddr(rw, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAddr(r io.Reader, b []byte) (Addr, error) {
|
func ReadAddr(r io.Reader, b []byte) (Addr, error) {
|
||||||
if len(b) < MaxAddrLen {
|
if len(b) < MaxAddrLen {
|
||||||
return nil, io.ErrShortBuffer
|
return nil, io.ErrShortBuffer
|
||||||
}
|
}
|
||||||
|
223
component/trojan/trojan.go
Normal file
223
component/trojan/trojan.go
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// max packet length
|
||||||
|
maxLength = 8192
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultALPN = []string{"h2", "http/1.1"}
|
||||||
|
crlf = []byte{'\r', '\n'}
|
||||||
|
|
||||||
|
bufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command = byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
CommandTCP byte = 1
|
||||||
|
CommandUDP byte = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
Password string
|
||||||
|
ALPN []string
|
||||||
|
ServerName string
|
||||||
|
SkipCertVerify bool
|
||||||
|
ClientSessionCache tls.ClientSessionCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type Trojan struct {
|
||||||
|
option *Option
|
||||||
|
hexPassword []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||||
|
alpn := defaultALPN
|
||||||
|
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,
|
||||||
|
ClientSessionCache: t.option.ClientSessionCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
defer buf.Reset()
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
|
||||||
|
buf.Write(t.hexPassword)
|
||||||
|
buf.Write(crlf)
|
||||||
|
|
||||||
|
buf.WriteByte(command)
|
||||||
|
buf.Write(socks5Addr)
|
||||||
|
buf.Write(crlf)
|
||||||
|
|
||||||
|
_, err := w.Write(buf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
|
||||||
|
return &PacketConn{
|
||||||
|
Conn: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||||
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
|
defer buf.Reset()
|
||||||
|
defer bufPool.Put(buf)
|
||||||
|
|
||||||
|
buf.Write(socks5Addr)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
|
||||||
|
buf.Write(crlf)
|
||||||
|
buf.Write(payload)
|
||||||
|
|
||||||
|
return w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||||
|
if len(payload) <= maxLength {
|
||||||
|
return writePacket(w, socks5Addr, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := 0
|
||||||
|
total := len(payload)
|
||||||
|
for {
|
||||||
|
cursor := offset + maxLength
|
||||||
|
if cursor > total {
|
||||||
|
cursor = total
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := writePacket(w, socks5Addr, payload[offset:cursor])
|
||||||
|
if err != nil {
|
||||||
|
return offset + n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = cursor
|
||||||
|
if offset == total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
|
||||||
|
addr, err := socks5.ReadAddr(r, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, errors.New("read addr error")
|
||||||
|
}
|
||||||
|
uAddr := addr.UDPAddr()
|
||||||
|
|
||||||
|
if _, err = io.ReadFull(r, payload[:2]); err != nil {
|
||||||
|
return nil, 0, 0, errors.New("read length error")
|
||||||
|
}
|
||||||
|
|
||||||
|
total := int(binary.BigEndian.Uint16(payload[:2]))
|
||||||
|
if total > maxLength {
|
||||||
|
return nil, 0, 0, errors.New("packet invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read crlf
|
||||||
|
if _, err = io.ReadFull(r, payload[:2]); err != nil {
|
||||||
|
return nil, 0, 0, errors.New("read crlf error")
|
||||||
|
}
|
||||||
|
|
||||||
|
length := len(payload)
|
||||||
|
if total < length {
|
||||||
|
length = total
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = io.ReadFull(r, payload[:length]); err != nil {
|
||||||
|
return nil, 0, 0, errors.New("read packet error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uAddr, length, total - length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(option *Option) *Trojan {
|
||||||
|
return &Trojan{option, hexSha224([]byte(option.Password))}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.Conn
|
||||||
|
remain int
|
||||||
|
rAddr net.Addr
|
||||||
|
mux sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
return WritePacket(pc, socks5.ParseAddr(addr.String()), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
pc.mux.Lock()
|
||||||
|
defer pc.mux.Unlock()
|
||||||
|
if pc.remain != 0 {
|
||||||
|
length := len(b)
|
||||||
|
if pc.remain < length {
|
||||||
|
length = pc.remain
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := pc.Conn.Read(b[:length])
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.remain -= n
|
||||||
|
addr := pc.rAddr
|
||||||
|
if pc.remain == 0 {
|
||||||
|
pc.rAddr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, n, remain, err := ReadPacket(pc.Conn, b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if remain != 0 {
|
||||||
|
pc.remain = remain
|
||||||
|
pc.rAddr = addr
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexSha224(data []byte) []byte {
|
||||||
|
buf := make([]byte, 56)
|
||||||
|
hash := sha256.New224()
|
||||||
|
hash.Write(data)
|
||||||
|
hex.Encode(buf, hash.Sum(nil))
|
||||||
|
return buf
|
||||||
|
}
|
@ -11,9 +11,12 @@ import (
|
|||||||
// Option is options of websocket obfs
|
// Option is options of websocket obfs
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Host string
|
Host string
|
||||||
|
Port string
|
||||||
Path string
|
Path string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
TLSConfig *tls.Config
|
TLS bool
|
||||||
|
SkipCertVerify bool
|
||||||
|
SessionCache tls.ClientSessionCache
|
||||||
Mux bool
|
Mux bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,14 +29,16 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) {
|
|||||||
|
|
||||||
config := &vmess.WebsocketConfig{
|
config := &vmess.WebsocketConfig{
|
||||||
Host: option.Host,
|
Host: option.Host,
|
||||||
|
Port: option.Port,
|
||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
TLS: option.TLSConfig != nil,
|
TLS: option.TLS,
|
||||||
Headers: header,
|
Headers: header,
|
||||||
TLSConfig: option.TLSConfig,
|
SkipCertVerify: option.SkipCertVerify,
|
||||||
|
SessionCache: option.SessionCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
conn, err = vmess.NewWebsocketConn(conn, config)
|
conn, err = vmess.StreamWebsocketConn(conn, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *aeadWriter) Write(b []byte) (n int, err error) {
|
func (w *aeadWriter) Write(b []byte) (n int, err error) {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
defer pool.BufPool.Put(buf[:cap(buf)])
|
defer pool.Put(buf)
|
||||||
length := len(b)
|
length := len(b)
|
||||||
for {
|
for {
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
@ -73,7 +73,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
|
|||||||
n := copy(b, r.buf[r.offset:])
|
n := copy(b, r.buf[r.offset:])
|
||||||
r.offset += n
|
r.offset += n
|
||||||
if r.offset == len(r.buf) {
|
if r.offset == len(r.buf) {
|
||||||
pool.BufPool.Put(r.buf[:cap(r.buf)])
|
pool.Put(r.buf)
|
||||||
r.buf = nil
|
r.buf = nil
|
||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
@ -89,10 +89,10 @@ func (r *aeadReader) Read(b []byte) (int, error) {
|
|||||||
return 0, errors.New("Buffer is larger than standard")
|
return 0, errors.New("Buffer is larger than standard")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(size)
|
||||||
_, err = io.ReadFull(r.Reader, buf[:size])
|
_, err = io.ReadFull(r.Reader, buf[:size])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
|
|||||||
realLen := size - r.Overhead()
|
realLen := size - r.Overhead()
|
||||||
n := copy(b, buf[:realLen])
|
n := copy(b, buf[:realLen])
|
||||||
if len(b) >= realLen {
|
if len(b) >= realLen {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
|
|||||||
n := copy(b, cr.buf[cr.offset:])
|
n := copy(b, cr.buf[cr.offset:])
|
||||||
cr.offset += n
|
cr.offset += n
|
||||||
if cr.offset == len(cr.buf) {
|
if cr.offset == len(cr.buf) {
|
||||||
pool.BufPool.Put(cr.buf[:cap(cr.buf)])
|
pool.Put(cr.buf)
|
||||||
cr.buf = nil
|
cr.buf = nil
|
||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
@ -59,15 +59,15 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
|
|||||||
return size, nil
|
return size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(size)
|
||||||
_, err = io.ReadFull(cr.Reader, buf[:size])
|
_, err = io.ReadFull(cr.Reader, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
n := copy(b, cr.buf[:])
|
n := copy(b, buf)
|
||||||
cr.offset = n
|
cr.offset = n
|
||||||
cr.buf = buf[:size]
|
cr.buf = buf
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +76,8 @@ type chunkWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cw *chunkWriter) Write(b []byte) (n int, err error) {
|
func (cw *chunkWriter) Write(b []byte) (n int, err error) {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
defer pool.BufPool.Put(buf[:cap(buf)])
|
defer pool.Put(buf)
|
||||||
length := len(b)
|
length := len(b)
|
||||||
for {
|
for {
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
|
76
component/vmess/http.go
Normal file
76
component/vmess/http.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpConn struct {
|
||||||
|
net.Conn
|
||||||
|
cfg *HTTPConfig
|
||||||
|
rhandshake bool
|
||||||
|
whandshake bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPConfig struct {
|
||||||
|
Method string
|
||||||
|
Host string
|
||||||
|
Path []string
|
||||||
|
Headers map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements net.Conn.Read()
|
||||||
|
func (hc *httpConn) Read(b []byte) (int, error) {
|
||||||
|
if hc.rhandshake {
|
||||||
|
n, err := hc.Conn.Read(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := textproto.NewConn(hc.Conn)
|
||||||
|
// First line: GET /index.html HTTP/1.0
|
||||||
|
if _, err := reader.ReadLine(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := reader.ReadMIMEHeader(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hc.rhandshake = true
|
||||||
|
return hc.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (hc *httpConn) Write(b []byte) (int, error) {
|
||||||
|
if hc.whandshake {
|
||||||
|
return hc.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))]
|
||||||
|
u := fmt.Sprintf("http://%s%s", hc.cfg.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))])
|
||||||
|
}
|
||||||
|
req.ContentLength = int64(len(b))
|
||||||
|
if err := req.Write(hc.Conn); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
hc.whandshake = true
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *httpConn) Close() error {
|
||||||
|
return hc.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn {
|
||||||
|
return &httpConn{
|
||||||
|
Conn: conn,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
24
component/vmess/tls.go
Normal file
24
component/vmess/tls.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLSConfig struct {
|
||||||
|
Host string
|
||||||
|
SkipCertVerify bool
|
||||||
|
SessionCache tls.ClientSessionCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
ServerName: cfg.Host,
|
||||||
|
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||||
|
ClientSessionCache: cfg.SessionCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
err := tlsConn.Handshake()
|
||||||
|
return tlsConn, err
|
||||||
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -69,10 +68,6 @@ type Client struct {
|
|||||||
user []*ID
|
user []*ID
|
||||||
uuid *uuid.UUID
|
uuid *uuid.UUID
|
||||||
security Security
|
security Security
|
||||||
tls bool
|
|
||||||
host string
|
|
||||||
wsConfig *WebsocketConfig
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config of vmess
|
// Config of vmess
|
||||||
@ -80,28 +75,13 @@ type Config struct {
|
|||||||
UUID string
|
UUID string
|
||||||
AlterID uint16
|
AlterID uint16
|
||||||
Security string
|
Security string
|
||||||
TLS bool
|
|
||||||
HostName string
|
|
||||||
Port string
|
Port string
|
||||||
NetWork string
|
HostName string
|
||||||
WebSocketPath string
|
|
||||||
WebSocketHeaders map[string]string
|
|
||||||
SkipCertVerify bool
|
|
||||||
SessionCache tls.ClientSessionCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return a Conn with net.Conn and DstAddr
|
// StreamConn return a Conn with net.Conn and DstAddr
|
||||||
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||||
var err error
|
|
||||||
r := rand.Intn(len(c.user))
|
r := rand.Intn(len(c.user))
|
||||||
if c.wsConfig != nil {
|
|
||||||
conn, err = NewWebsocketConn(conn, c.wsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if c.tls {
|
|
||||||
conn = tls.Client(conn, c.tlsConfig)
|
|
||||||
}
|
|
||||||
return newConn(conn, c.user[r], dst, c.security)
|
return newConn(conn, c.user[r], dst, c.security)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,57 +109,9 @@ func NewClient(config Config) (*Client, error) {
|
|||||||
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.NetWork != "" && config.NetWork != "ws" {
|
|
||||||
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
|
|
||||||
}
|
|
||||||
|
|
||||||
header := http.Header{}
|
|
||||||
for k, v := range config.WebSocketHeaders {
|
|
||||||
header.Add(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
host := net.JoinHostPort(config.HostName, config.Port)
|
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
if config.TLS {
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
ServerName: config.HostName,
|
|
||||||
InsecureSkipVerify: config.SkipCertVerify,
|
|
||||||
ClientSessionCache: config.SessionCache,
|
|
||||||
}
|
|
||||||
if tlsConfig.ClientSessionCache == nil {
|
|
||||||
tlsConfig.ClientSessionCache = getClientSessionCache()
|
|
||||||
}
|
|
||||||
if host := header.Get("Host"); host != "" {
|
|
||||||
tlsConfig.ServerName = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wsConfig *WebsocketConfig
|
|
||||||
if config.NetWork == "ws" {
|
|
||||||
wsConfig = &WebsocketConfig{
|
|
||||||
Host: host,
|
|
||||||
Path: config.WebSocketPath,
|
|
||||||
Headers: header,
|
|
||||||
TLS: config.TLS,
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
user: newAlterIDs(newID(&uid), config.AlterID),
|
user: newAlterIDs(newID(&uid), config.AlterID),
|
||||||
uuid: &uid,
|
uuid: &uid,
|
||||||
security: security,
|
security: security,
|
||||||
tls: config.TLS,
|
|
||||||
host: host,
|
|
||||||
wsConfig: wsConfig,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientSessionCache() tls.ClientSessionCache {
|
|
||||||
once.Do(func() {
|
|
||||||
clientSessionCache = tls.NewLRUClientSessionCache(128)
|
|
||||||
})
|
|
||||||
return clientSessionCache
|
|
||||||
}
|
|
||||||
|
@ -26,10 +26,12 @@ type websocketConn struct {
|
|||||||
|
|
||||||
type WebsocketConfig struct {
|
type WebsocketConfig struct {
|
||||||
Host string
|
Host string
|
||||||
|
Port string
|
||||||
Path string
|
Path string
|
||||||
Headers http.Header
|
Headers http.Header
|
||||||
TLS bool
|
TLS bool
|
||||||
TLSConfig *tls.Config
|
SkipCertVerify bool
|
||||||
|
SessionCache tls.ClientSessionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read implements net.Conn.Read()
|
// Read implements net.Conn.Read()
|
||||||
@ -111,7 +113,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return wsc.conn.SetWriteDeadline(t)
|
return wsc.conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||||
dialer := &websocket.Dialer{
|
dialer := &websocket.Dialer{
|
||||||
NetDial: func(network, addr string) (net.Conn, error) {
|
NetDial: func(network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
@ -124,17 +126,20 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
|||||||
scheme := "ws"
|
scheme := "ws"
|
||||||
if c.TLS {
|
if c.TLS {
|
||||||
scheme = "wss"
|
scheme = "wss"
|
||||||
dialer.TLSClientConfig = c.TLSConfig
|
dialer.TLSClientConfig = &tls.Config{
|
||||||
|
ServerName: c.Host,
|
||||||
|
InsecureSkipVerify: c.SkipCertVerify,
|
||||||
|
ClientSessionCache: c.SessionCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(c.Host)
|
if host := c.Headers.Get("Host"); host != "" {
|
||||||
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
|
dialer.TLSClientConfig.ServerName = host
|
||||||
host = c.Host
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := url.URL{
|
uri := url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Host: host,
|
Host: net.JoinHostPort(c.Host, c.Port),
|
||||||
Path: c.Path,
|
Path: c.Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +156,7 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
|||||||
if resp != nil {
|
if resp != nil {
|
||||||
reason = resp.Status
|
reason = resp.Status
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Dial %s error: %s", host, reason)
|
return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &websocketConn{
|
return &websocketConn{
|
||||||
|
129
config/config.go
129
config/config.go
@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -30,7 +31,7 @@ type General struct {
|
|||||||
Authentication []string `json:"authentication"`
|
Authentication []string `json:"authentication"`
|
||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
BindAddress string `json:"bind-address"`
|
BindAddress string `json:"bind-address"`
|
||||||
Mode T.Mode `json:"mode"`
|
Mode T.TunnelMode `json:"mode"`
|
||||||
LogLevel log.LogLevel `json:"log-level"`
|
LogLevel log.LogLevel `json:"log-level"`
|
||||||
ExternalController string `json:"-"`
|
ExternalController string `json:"-"`
|
||||||
ExternalUI string `json:"-"`
|
ExternalUI string `json:"-"`
|
||||||
@ -46,6 +47,7 @@ type DNS struct {
|
|||||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||||
|
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||||
FakeIPRange *fakeip.Pool
|
FakeIPRange *fakeip.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +60,7 @@ type FallbackFilter struct {
|
|||||||
// Experimental config
|
// Experimental config
|
||||||
type Experimental struct {
|
type Experimental struct {
|
||||||
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
||||||
|
Interface string `yaml:"interface-name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is clash config manager
|
// Config is clash config manager
|
||||||
@ -72,51 +75,65 @@ type Config struct {
|
|||||||
Providers map[string]provider.ProxyProvider
|
Providers map[string]provider.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawDNS struct {
|
type RawDNS struct {
|
||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
NameServer []string `yaml:"nameserver"`
|
NameServer []string `yaml:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
FallbackFilter rawFallbackFilter `yaml:"fallback-filter"`
|
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||||
FakeIPRange string `yaml:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||||
|
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
IPCIDR []string `yaml:"ipcidr"`
|
IPCIDR []string `yaml:"ipcidr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawConfig struct {
|
type RawConfig struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
RedirPort int `yaml:"redir-port"`
|
RedirPort int `yaml:"redir-port"`
|
||||||
Authentication []string `yaml:"authentication"`
|
Authentication []string `yaml:"authentication"`
|
||||||
AllowLan bool `yaml:"allow-lan"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
BindAddress string `yaml:"bind-address"`
|
BindAddress string `yaml:"bind-address"`
|
||||||
Mode T.Mode `yaml:"mode"`
|
Mode T.TunnelMode `yaml:"mode"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
ExternalController string `yaml:"external-controller"`
|
ExternalController string `yaml:"external-controller"`
|
||||||
ExternalUI string `yaml:"external-ui"`
|
ExternalUI string `yaml:"external-ui"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
|
|
||||||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"`
|
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
DNS rawDNS `yaml:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Experimental Experimental `yaml:"experimental"`
|
Experimental Experimental `yaml:"experimental"`
|
||||||
Proxy []map[string]interface{} `yaml:"Proxy"`
|
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
|
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"Rule"`
|
Rule []string `yaml:"rules"`
|
||||||
|
|
||||||
|
// remove after 1.0
|
||||||
|
ProxyProviderOld map[string]map[string]interface{} `yaml:"proxy-provider"`
|
||||||
|
ProxyOld []map[string]interface{} `yaml:"Proxy"`
|
||||||
|
ProxyGroupOld []map[string]interface{} `yaml:"Proxy Group"`
|
||||||
|
RuleOld []string `yaml:"Rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse config
|
// Parse config
|
||||||
func Parse(buf []byte) (*Config, error) {
|
func Parse(buf []byte) (*Config, error) {
|
||||||
config := &Config{}
|
rawCfg, err := UnmarshalRawConfig(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRawConfig(rawCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
// config with some default value
|
// config with some default value
|
||||||
rawCfg := &rawConfig{
|
rawCfg := &RawConfig{
|
||||||
AllowLan: false,
|
AllowLan: false,
|
||||||
BindAddress: "*",
|
BindAddress: "*",
|
||||||
Mode: T.Rule,
|
Mode: T.Rule,
|
||||||
@ -129,19 +146,35 @@ func Parse(buf []byte) (*Config, error) {
|
|||||||
Experimental: Experimental{
|
Experimental: Experimental{
|
||||||
IgnoreResolveFail: true,
|
IgnoreResolveFail: true,
|
||||||
},
|
},
|
||||||
DNS: rawDNS{
|
DNS: RawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
FallbackFilter: rawFallbackFilter{
|
FallbackFilter: RawFallbackFilter{
|
||||||
GeoIP: true,
|
GeoIP: true,
|
||||||
IPCIDR: []string{},
|
IPCIDR: []string{},
|
||||||
},
|
},
|
||||||
|
DefaultNameserver: []string{
|
||||||
|
"114.114.114.114",
|
||||||
|
"8.8.8.8",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// remove after 1.0
|
||||||
|
RuleOld: []string{},
|
||||||
|
ProxyOld: []map[string]interface{}{},
|
||||||
|
ProxyGroupOld: []map[string]interface{}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rawCfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
config.Experimental = &rawCfg.Experimental
|
config.Experimental = &rawCfg.Experimental
|
||||||
|
|
||||||
general, err := parseGeneral(rawCfg)
|
general, err := parseGeneral(rawCfg)
|
||||||
@ -176,10 +209,11 @@ func Parse(buf []byte) (*Config, error) {
|
|||||||
config.Hosts = hosts
|
config.Hosts = hosts
|
||||||
|
|
||||||
config.Users = parseAuthentication(rawCfg.Authentication)
|
config.Users = parseAuthentication(rawCfg.Authentication)
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGeneral(cfg *rawConfig) (*General, error) {
|
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
port := cfg.Port
|
port := cfg.Port
|
||||||
socksPort := cfg.SocksPort
|
socksPort := cfg.SocksPort
|
||||||
redirPort := cfg.RedirPort
|
redirPort := cfg.RedirPort
|
||||||
@ -192,7 +226,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
|||||||
logLevel := cfg.LogLevel
|
logLevel := cfg.LogLevel
|
||||||
|
|
||||||
if externalUI != "" {
|
if externalUI != "" {
|
||||||
externalUI = C.Path.Reslove(externalUI)
|
externalUI = C.Path.Resolve(externalUI)
|
||||||
|
|
||||||
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
|
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
|
||||||
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
||||||
@ -214,7 +248,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
|||||||
return general, nil
|
return general, 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]provider.ProxyProvider, err error) {
|
||||||
proxies = make(map[string]C.Proxy)
|
proxies = make(map[string]C.Proxy)
|
||||||
providersMap = make(map[string]provider.ProxyProvider)
|
providersMap = make(map[string]provider.ProxyProvider)
|
||||||
proxyList := []string{}
|
proxyList := []string{}
|
||||||
@ -222,14 +256,17 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
groupsConfig := cfg.ProxyGroup
|
groupsConfig := cfg.ProxyGroup
|
||||||
providersConfig := cfg.ProxyProvider
|
providersConfig := cfg.ProxyProvider
|
||||||
|
|
||||||
defer func() {
|
if len(proxiesConfig) == 0 {
|
||||||
// Destroy already created provider when err != nil
|
proxiesConfig = cfg.ProxyOld
|
||||||
if err != nil {
|
|
||||||
for _, provider := range providersMap {
|
|
||||||
provider.Destroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(groupsConfig) == 0 {
|
||||||
|
groupsConfig = cfg.ProxyGroupOld
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(providersConfig) == 0 {
|
||||||
|
providersConfig = cfg.ProxyProviderOld
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
||||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
||||||
@ -249,7 +286,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
proxyList = append(proxyList, proxy.Name())
|
proxyList = append(proxyList, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the origional order of ProxyGroups in config file
|
// keep the original order of ProxyGroups in config file
|
||||||
for idx, mapping := range groupsConfig {
|
for idx, mapping := range groupsConfig {
|
||||||
groupName, existName := mapping["name"].(string)
|
groupName, existName := mapping["name"].(string)
|
||||||
if !existName {
|
if !existName {
|
||||||
@ -299,7 +336,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
proxies[groupName] = outbound.NewProxy(group)
|
proxies[groupName] = outbound.NewProxy(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial compatible provier
|
// initial compatible provider
|
||||||
for _, pd := range providersMap {
|
for _, pd := range providersMap {
|
||||||
if pd.VehicleType() != provider.Compatible {
|
if pd.VehicleType() != provider.Compatible {
|
||||||
continue
|
continue
|
||||||
@ -316,7 +353,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
ps = append(ps, proxies[v])
|
ps = append(ps, proxies[v])
|
||||||
}
|
}
|
||||||
hc := provider.NewHealthCheck(ps, "", 0)
|
hc := provider.NewHealthCheck(ps, "", 0)
|
||||||
pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, hc)
|
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||||
providersMap[provider.ReservedName] = pd
|
providersMap[provider.ReservedName] = pd
|
||||||
|
|
||||||
global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd})
|
global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd})
|
||||||
@ -324,10 +361,16 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
return proxies, providersMap, nil
|
return proxies, providersMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||||
rules := []C.Rule{}
|
rules := []C.Rule{}
|
||||||
|
|
||||||
rulesConfig := cfg.Rule
|
rulesConfig := cfg.Rule
|
||||||
|
|
||||||
|
// remove after 1.0
|
||||||
|
if len(rulesConfig) == 0 {
|
||||||
|
rulesConfig = cfg.RuleOld
|
||||||
|
}
|
||||||
|
|
||||||
// parse rules
|
// parse rules
|
||||||
for idx, line := range rulesConfig {
|
for idx, line := range rulesConfig {
|
||||||
rule := trimArr(strings.Split(line, ","))
|
rule := trimArr(strings.Split(line, ","))
|
||||||
@ -403,7 +446,7 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHosts(cfg *rawConfig) (*trie.Trie, error) {
|
func parseHosts(cfg *RawConfig) (*trie.Trie, error) {
|
||||||
tree := trie.New()
|
tree := trie.New()
|
||||||
if len(cfg.Hosts) != 0 {
|
if len(cfg.Hosts) != 0 {
|
||||||
for domain, ipStr := range cfg.Hosts {
|
for domain, ipStr := range cfg.Hosts {
|
||||||
@ -448,20 +491,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var host, dnsNetType string
|
var addr, dnsNetType string
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "udp":
|
case "udp":
|
||||||
host, err = hostWithDefaultPort(u.Host, "53")
|
addr, err = hostWithDefaultPort(u.Host, "53")
|
||||||
dnsNetType = "" // UDP
|
dnsNetType = "" // UDP
|
||||||
case "tcp":
|
case "tcp":
|
||||||
host, err = hostWithDefaultPort(u.Host, "53")
|
addr, err = hostWithDefaultPort(u.Host, "53")
|
||||||
dnsNetType = "tcp" // TCP
|
dnsNetType = "tcp" // TCP
|
||||||
case "tls":
|
case "tls":
|
||||||
host, err = hostWithDefaultPort(u.Host, "853")
|
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||||
dnsNetType = "tcp-tls" // DNS over TLS
|
dnsNetType = "tcp-tls" // DNS over TLS
|
||||||
case "https":
|
case "https":
|
||||||
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
||||||
host = clearURL.String()
|
addr = clearURL.String()
|
||||||
dnsNetType = "https" // DNS over HTTPS
|
dnsNetType = "https" // DNS over HTTPS
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||||
@ -475,7 +518,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
nameservers,
|
nameservers,
|
||||||
dns.NameServer{
|
dns.NameServer{
|
||||||
Net: dnsNetType,
|
Net: dnsNetType,
|
||||||
Addr: host,
|
Addr: addr,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -496,7 +539,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
|||||||
return ipNets, nil
|
return ipNets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDNS(cfg rawDNS) (*DNS, error) {
|
func parseDNS(cfg RawDNS) (*DNS, error) {
|
||||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||||
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
|
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
|
||||||
}
|
}
|
||||||
@ -519,6 +562,20 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.DefaultNameserver) == 0 {
|
||||||
|
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||||
|
}
|
||||||
|
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// check default nameserver is pure ip addr
|
||||||
|
for _, ns := range dnsCfg.DefaultNameserver {
|
||||||
|
host, _, err := net.SplitHostPort(ns.Addr)
|
||||||
|
if err != nil || net.ParseIP(host) == nil {
|
||||||
|
return nil, errors.New("default nameserver should be pure IP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.EnhancedMode == dns.FAKEIP {
|
if cfg.EnhancedMode == dns.FAKEIP {
|
||||||
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
|
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,52 +1,49 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func downloadMMDB(path string) (err error) {
|
func downloadMMDB(path string) (err error) {
|
||||||
resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz")
|
resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
gr, err := gzip.NewReader(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer gr.Close()
|
|
||||||
|
|
||||||
tr := tar.NewReader(gr)
|
|
||||||
for {
|
|
||||||
h, err := tr.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
_, err = io.Copy(f, tr)
|
_, err = io.Copy(f, resp.Body)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func initMMDB() error {
|
||||||
|
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||||
|
log.Infoln("Can't find MMDB, start download")
|
||||||
|
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||||
|
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mmdb.Verify() {
|
||||||
|
log.Warnln("MMDB invalid, remove and download")
|
||||||
|
if err := os.Remove(C.Path.MMDB()); err != nil {
|
||||||
|
return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||||
|
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,17 +61,18 @@ func Init(dir string) error {
|
|||||||
|
|
||||||
// initial config.yaml
|
// initial config.yaml
|
||||||
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find config, create an empty file")
|
log.Infoln("Can't find config, create a initial config file")
|
||||||
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, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error())
|
||||||
|
}
|
||||||
|
f.Write([]byte(`port: 7890`))
|
||||||
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial mmdb
|
// initial mmdb
|
||||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
if err := initMMDB(); err != nil {
|
||||||
log.Infoln("Can't find MMDB, start download")
|
return fmt.Errorf("Can't initial MMDB: %w", err)
|
||||||
err := downloadMMDB(C.Path.MMDB())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,19 @@ import (
|
|||||||
// Adapter Type
|
// Adapter Type
|
||||||
const (
|
const (
|
||||||
Direct AdapterType = iota
|
Direct AdapterType = iota
|
||||||
Fallback
|
|
||||||
Reject
|
Reject
|
||||||
Selector
|
|
||||||
Shadowsocks
|
Shadowsocks
|
||||||
Snell
|
Snell
|
||||||
Socks5
|
Socks5
|
||||||
Http
|
Http
|
||||||
URLTest
|
|
||||||
Vmess
|
Vmess
|
||||||
|
Trojan
|
||||||
|
|
||||||
|
Relay
|
||||||
|
Selector
|
||||||
|
Fallback
|
||||||
|
URLTest
|
||||||
LoadBalance
|
LoadBalance
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -53,15 +57,20 @@ type Conn interface {
|
|||||||
type PacketConn interface {
|
type PacketConn interface {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
Connection
|
Connection
|
||||||
|
WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
|
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
|
||||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||||
DialUDP(metadata *Metadata) (PacketConn, net.Addr, error)
|
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||||
SupportUDP() bool
|
SupportUDP() bool
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
|
Addr() string
|
||||||
|
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
||||||
|
Unwrap(metadata *Metadata) Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
type DelayHistory struct {
|
type DelayHistory struct {
|
||||||
@ -85,12 +94,9 @@ func (at AdapterType) String() string {
|
|||||||
switch at {
|
switch at {
|
||||||
case Direct:
|
case Direct:
|
||||||
return "Direct"
|
return "Direct"
|
||||||
case Fallback:
|
|
||||||
return "Fallback"
|
|
||||||
case Reject:
|
case Reject:
|
||||||
return "Reject"
|
return "Reject"
|
||||||
case Selector:
|
|
||||||
return "Selector"
|
|
||||||
case Shadowsocks:
|
case Shadowsocks:
|
||||||
return "Shadowsocks"
|
return "Shadowsocks"
|
||||||
case Snell:
|
case Snell:
|
||||||
@ -99,12 +105,22 @@ func (at AdapterType) String() string {
|
|||||||
return "Socks5"
|
return "Socks5"
|
||||||
case Http:
|
case Http:
|
||||||
return "Http"
|
return "Http"
|
||||||
case URLTest:
|
|
||||||
return "URLTest"
|
|
||||||
case Vmess:
|
case Vmess:
|
||||||
return "Vmess"
|
return "Vmess"
|
||||||
|
case Trojan:
|
||||||
|
return "Trojan"
|
||||||
|
|
||||||
|
case Relay:
|
||||||
|
return "Relay"
|
||||||
|
case Selector:
|
||||||
|
return "Selector"
|
||||||
|
case Fallback:
|
||||||
|
return "Fallback"
|
||||||
|
case URLTest:
|
||||||
|
return "URLTest"
|
||||||
case LoadBalance:
|
case LoadBalance:
|
||||||
return "LoadBalance"
|
return "LoadBalance"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
@ -121,8 +137,8 @@ type UDPPacket interface {
|
|||||||
// this is important when using Fake-IP.
|
// this is important when using Fake-IP.
|
||||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||||
|
|
||||||
// Close closes the underlaying connection.
|
// Drop call after packet is used, could recycle buffer in this function.
|
||||||
Close() error
|
Drop()
|
||||||
|
|
||||||
// LocalAddr returns the source IP/Port of packet
|
// LocalAddr returns the source IP/Port of packet
|
||||||
LocalAddr() net.Addr
|
LocalAddr() net.Addr
|
||||||
|
@ -3,6 +3,7 @@ package constant
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socks addr type
|
// Socks addr type
|
||||||
@ -70,6 +71,25 @@ func (m *Metadata) RemoteAddress() string {
|
|||||||
return net.JoinHostPort(m.String(), m.DstPort)
|
return net.JoinHostPort(m.String(), m.DstPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) SourceAddress() string {
|
||||||
|
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) Resolved() bool {
|
||||||
|
return m.DstIP != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
||||||
|
if m.NetWork != UDP || m.DstIP == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
port, _ := strconv.Atoi(m.DstPort)
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: m.DstIP,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Metadata) String() string {
|
func (m *Metadata) String() string {
|
||||||
if m.Host != "" {
|
if m.Host != "" {
|
||||||
return m.Host
|
return m.Host
|
||||||
|
@ -44,8 +44,8 @@ func (p *path) Config() string {
|
|||||||
return p.configFile
|
return p.configFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reslove return a absolute path or a relative path with homedir
|
// Resolve return a absolute path or a relative path with homedir
|
||||||
func (p *path) Reslove(path string) string {
|
func (p *path) Resolve(path string) string {
|
||||||
if !filepath.IsAbs(path) {
|
if !filepath.IsAbs(path) {
|
||||||
return filepath.Join(p.HomeDir(), path)
|
return filepath.Join(p.HomeDir(), path)
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,20 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
*D.Client
|
*D.Client
|
||||||
Address string
|
r *Resolver
|
||||||
|
port string
|
||||||
|
host string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
@ -16,6 +23,50 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address)
|
var ip net.IP
|
||||||
return
|
if c.r == nil {
|
||||||
|
// a default ip dns
|
||||||
|
ip = net.ParseIP(c.host)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
if ip, err = c.r.ResolveIP(c.host); err != nil {
|
||||||
|
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := dialer.Dialer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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
|
||||||
|
|
||||||
|
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||||
|
// this is a workaround
|
||||||
|
type result struct {
|
||||||
|
msg *D.Msg
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
ch := make(chan result, 1)
|
||||||
|
go func() {
|
||||||
|
msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port))
|
||||||
|
ch <- result{msg, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case ret := <-ch:
|
||||||
|
return ret.msg, ret.err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
32
dns/doh.go
32
dns/doh.go
@ -5,8 +5,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,12 +18,9 @@ const (
|
|||||||
dotMimeType = "application/dns-message"
|
dotMimeType = "application/dns-message"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dohTransport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
|
||||||
}
|
|
||||||
|
|
||||||
type dohClient struct {
|
type dohClient struct {
|
||||||
url string
|
url string
|
||||||
|
transport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
@ -55,7 +55,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||||
client := &http.Client{Transport: dohTransport}
|
client := &http.Client{Transport: dc.transport}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -70,3 +70,25 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
|||||||
err = msg.Unpack(buf)
|
err = msg.Unpack(buf)
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newDoHClient(url string, r *Resolver) *dohClient {
|
||||||
|
return &dohClient{
|
||||||
|
url: url,
|
||||||
|
transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := r.ResolveIPv4(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
|
)
|
||||||
|
|
||||||
type fallbackFilter interface {
|
type fallbackFilter interface {
|
||||||
Match(net.IP) bool
|
Match(net.IP) bool
|
||||||
@ -9,12 +13,8 @@ type fallbackFilter interface {
|
|||||||
type geoipFilter struct{}
|
type geoipFilter struct{}
|
||||||
|
|
||||||
func (gf *geoipFilter) Match(ip net.IP) bool {
|
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||||
if mmdb == nil {
|
record, _ := mmdb.Instance().Country(ip)
|
||||||
return false
|
return record.Country.IsoCode != "CN" && record.Country.IsoCode != ""
|
||||||
}
|
|
||||||
|
|
||||||
record, _ := mmdb.Country(ip)
|
|
||||||
return record.Country.IsoCode == "CN" || record.Country.IsoCode == ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ipnetFilter struct {
|
type ipnetFilter struct {
|
||||||
|
@ -18,7 +18,14 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
if q.Qtype == D.TypeAAAA {
|
if q.Qtype == D.TypeAAAA {
|
||||||
D.HandleFailed(w, r)
|
msg := &D.Msg{}
|
||||||
|
msg.Answer = []D.RR{}
|
||||||
|
|
||||||
|
msg.SetRcode(r, D.RcodeSuccess)
|
||||||
|
msg.Authoritative = true
|
||||||
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
|
w.WriteMsg(msg)
|
||||||
return
|
return
|
||||||
} else if q.Qtype != D.TypeA {
|
} else if q.Qtype != D.TypeA {
|
||||||
next(w, r)
|
next(w, r)
|
||||||
@ -39,7 +46,10 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
msg.Answer = []D.RR{rr}
|
msg.Answer = []D.RR{rr}
|
||||||
|
|
||||||
setMsgTTL(msg, 1)
|
setMsgTTL(msg, 1)
|
||||||
msg.SetReply(r)
|
msg.SetRcode(r, D.RcodeSuccess)
|
||||||
|
msg.Authoritative = true
|
||||||
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
w.WriteMsg(msg)
|
w.WriteMsg(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -55,7 +65,8 @@ func withResolver(resolver *Resolver) handler {
|
|||||||
D.HandleFailed(w, r)
|
D.HandleFailed(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg.SetReply(r)
|
msg.SetRcode(r, msg.Rcode)
|
||||||
|
msg.Authoritative = true
|
||||||
w.WriteMsg(msg)
|
w.WriteMsg(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
123
dns/resolver.go
123
dns/resolver.go
@ -4,38 +4,26 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/common/picker"
|
"github.com/Dreamacro/clash/common/picker"
|
||||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
geoip2 "github.com/oschwald/geoip2-golang"
|
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultResolver aim to resolve ip
|
|
||||||
DefaultResolver *Resolver
|
|
||||||
|
|
||||||
// DefaultHosts aim to resolve hosts
|
|
||||||
DefaultHosts = trie.New()
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
||||||
|
|
||||||
mmdb *geoip2.Reader
|
|
||||||
once sync.Once
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type resolver interface {
|
type dnsClient interface {
|
||||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||||
}
|
}
|
||||||
@ -50,36 +38,33 @@ type Resolver struct {
|
|||||||
mapping bool
|
mapping bool
|
||||||
fakeip bool
|
fakeip bool
|
||||||
pool *fakeip.Pool
|
pool *fakeip.Pool
|
||||||
main []resolver
|
main []dnsClient
|
||||||
fallback []resolver
|
fallback []dnsClient
|
||||||
fallbackFilters []fallbackFilter
|
fallbackFilters []fallbackFilter
|
||||||
group singleflight.Group
|
group singleflight.Group
|
||||||
cache *cache.Cache
|
lruCache *cache.LruCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA
|
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||||
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
||||||
ch := make(chan net.IP)
|
ch := make(chan net.IP, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
ip, err := r.resolveIP(host, D.TypeA)
|
ip, err := r.resolveIP(host, D.TypeAAAA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch <- ip
|
ch <- ip
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ip, err = r.resolveIP(host, D.TypeAAAA)
|
ip, err = r.resolveIP(host, D.TypeA)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
go func() {
|
|
||||||
<-ch
|
|
||||||
}()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, open := <-ch
|
ip, open := <-ch
|
||||||
if !open {
|
if !open {
|
||||||
return nil, errIPNotFound
|
return nil, resolver.ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip, nil
|
return ip, nil
|
||||||
@ -111,31 +96,43 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := m.Question[0]
|
q := m.Question[0]
|
||||||
cache, expireTime := r.cache.GetWithExpire(q.String())
|
cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
|
||||||
if cache != nil {
|
if hit {
|
||||||
|
now := time.Now()
|
||||||
msg = cache.(*D.Msg).Copy()
|
msg = cache.(*D.Msg).Copy()
|
||||||
|
if expireTime.Before(now) {
|
||||||
|
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||||
|
go r.exchangeWithoutCache(m)
|
||||||
|
} else {
|
||||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return r.exchangeWithoutCache(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) {
|
||||||
|
q := m.Question[0]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putMsgToCache(r.cache, q.String(), msg)
|
putMsgToCache(r.lruCache, q.String(), msg)
|
||||||
if r.mapping {
|
if r.mapping {
|
||||||
ips := r.msgToIP(msg)
|
ips := r.msgToIP(msg)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
putMsgToCache(r.cache, ip.String(), msg)
|
putMsgToCache(r.lruCache, ip.String(), msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ret, err, _ := r.group.Do(q.String(), func() (interface{}, error) {
|
ret, err, shared := r.group.Do(q.String(), func() (interface{}, error) {
|
||||||
isIPReq := isIPRequest(q)
|
isIPReq := isIPRequest(q)
|
||||||
if isIPReq {
|
if isIPReq {
|
||||||
msg, err := r.fallbackExchange(m)
|
return r.fallbackExchange(m)
|
||||||
return msg, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.batchExchange(r.main, m)
|
return r.batchExchange(r.main, m)
|
||||||
@ -143,6 +140,9 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
msg = ret.(*D.Msg)
|
msg = ret.(*D.Msg)
|
||||||
|
if shared {
|
||||||
|
msg = msg.Copy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -154,7 +154,7 @@ func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
|||||||
return r.pool.LookBack(ip)
|
return r.pool.LookBack(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
cache := r.cache.Get(ip.String())
|
cache, _ := r.lruCache.Get(ip.String())
|
||||||
if cache == nil {
|
if cache == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
@ -179,22 +179,28 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second)
|
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
r := client
|
r := client
|
||||||
fast.Go(func() (interface{}, error) {
|
fast.Go(func() (interface{}, error) {
|
||||||
msg, err := r.ExchangeContext(ctx, m)
|
m, err := r.ExchangeContext(ctx, m)
|
||||||
if err != nil || msg.Rcode != D.RcodeSuccess {
|
if err != nil {
|
||||||
return nil, errors.New("resolve error")
|
return nil, err
|
||||||
|
} else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused {
|
||||||
|
return nil, errors.New("server failure")
|
||||||
}
|
}
|
||||||
return msg, nil
|
return m, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
elm := fast.Wait()
|
elm := fast.Wait()
|
||||||
if elm == nil {
|
if elm == nil {
|
||||||
return nil, errors.New("All DNS requests failed")
|
err := errors.New("All DNS requests failed")
|
||||||
|
if fErr := fast.Error(); fErr != nil {
|
||||||
|
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = elm.(*D.Msg)
|
msg = elm.(*D.Msg)
|
||||||
@ -212,9 +218,9 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||||
if r.shouldFallback(ips[0]) {
|
if !r.shouldFallback(ips[0]) {
|
||||||
go func() { <-fallbackMsg }()
|
|
||||||
msg = res.Msg
|
msg = res.Msg
|
||||||
|
err = res.Error
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,6 +239,8 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
|||||||
return ip, nil
|
return ip, nil
|
||||||
} else if dnsType == D.TypeA && isIPv4 {
|
} else if dnsType == D.TypeA && isIPv4 {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
|
} else {
|
||||||
|
return nil, resolver.ErrIPVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,11 +253,12 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ips := r.msgToIP(msg)
|
ips := r.msgToIP(msg)
|
||||||
if len(ips) == 0 {
|
ipLength := len(ips)
|
||||||
return nil, errIPNotFound
|
if ipLength == 0 {
|
||||||
|
return nil, resolver.ErrIPNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = ips[0]
|
ip = ips[rand.Intn(ipLength)]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,8 +277,8 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
|||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
|
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
||||||
ch := make(chan *result)
|
ch := make(chan *result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
res, err := r.batchExchange(client, msg)
|
res, err := r.batchExchange(client, msg)
|
||||||
ch <- &result{Msg: res, Error: err}
|
ch <- &result{Msg: res, Error: err}
|
||||||
@ -289,6 +298,7 @@ type FallbackFilter struct {
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Main, Fallback []NameServer
|
Main, Fallback []NameServer
|
||||||
|
Default []NameServer
|
||||||
IPv6 bool
|
IPv6 bool
|
||||||
EnhancedMode EnhancedMode
|
EnhancedMode EnhancedMode
|
||||||
FallbackFilter FallbackFilter
|
FallbackFilter FallbackFilter
|
||||||
@ -296,25 +306,26 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(config Config) *Resolver {
|
func New(config Config) *Resolver {
|
||||||
|
defaultResolver := &Resolver{
|
||||||
|
main: transform(config.Default, nil),
|
||||||
|
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||||
|
}
|
||||||
|
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: transform(config.Main),
|
main: transform(config.Main, defaultResolver),
|
||||||
cache: cache.New(time.Second * 60),
|
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||||
mapping: config.EnhancedMode == MAPPING,
|
mapping: config.EnhancedMode == MAPPING,
|
||||||
fakeip: config.EnhancedMode == FAKEIP,
|
fakeip: config.EnhancedMode == FAKEIP,
|
||||||
pool: config.Pool,
|
pool: config.Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Fallback) != 0 {
|
if len(config.Fallback) != 0 {
|
||||||
r.fallback = transform(config.Fallback)
|
r.fallback = transform(config.Fallback, defaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
fallbackFilters := []fallbackFilter{}
|
fallbackFilters := []fallbackFilter{}
|
||||||
if config.FallbackFilter.GeoIP {
|
if config.FallbackFilter.GeoIP {
|
||||||
once.Do(func() {
|
|
||||||
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
|
||||||
})
|
|
||||||
|
|
||||||
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
||||||
}
|
}
|
||||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||||
|
@ -3,6 +3,8 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,6 +60,11 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = sockopt.UDPReuseaddr(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
address = addr
|
address = addr
|
||||||
handler := newHandler(resolver)
|
handler := newHandler(resolver)
|
||||||
server = &Server{handler: handler}
|
server = &Server{handler: handler}
|
||||||
|
31
dns/util.go
31
dns/util.go
@ -4,11 +4,11 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@ -45,8 +45,8 @@ func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MarshalYAML serialize EnhancedMode with yaml
|
// MarshalYAML serialize EnhancedMode with yaml
|
||||||
func (e EnhancedMode) MarshalYAML() ([]byte, error) {
|
func (e EnhancedMode) MarshalYAML() (interface{}, error) {
|
||||||
return yaml.Marshal(e.String())
|
return e.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON unserialize EnhancedMode with json
|
// UnmarshalJSON unserialize EnhancedMode with json
|
||||||
@ -79,21 +79,21 @@ func (e EnhancedMode) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) {
|
func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
|
||||||
var ttl time.Duration
|
var ttl uint32
|
||||||
switch {
|
switch {
|
||||||
case len(msg.Answer) != 0:
|
case len(msg.Answer) != 0:
|
||||||
ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second
|
ttl = msg.Answer[0].Header().Ttl
|
||||||
case len(msg.Ns) != 0:
|
case len(msg.Ns) != 0:
|
||||||
ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second
|
ttl = msg.Ns[0].Header().Ttl
|
||||||
case len(msg.Extra) != 0:
|
case len(msg.Extra) != 0:
|
||||||
ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second
|
ttl = msg.Extra[0].Header().Ttl
|
||||||
default:
|
default:
|
||||||
log.Debugln("[DNS] response msg error: %#v", msg)
|
log.Debugln("[DNS] response msg error: %#v", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Put(key, msg.Copy(), ttl)
|
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||||
@ -117,14 +117,15 @@ func isIPRequest(q D.Question) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(servers []NameServer) []resolver {
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||||
ret := []resolver{}
|
ret := []dnsClient{}
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
if s.Net == "https" {
|
if s.Net == "https" {
|
||||||
ret = append(ret, &dohClient{url: s.Addr})
|
ret = append(ret, newDoHClient(s.Addr, resolver))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host, port, _ := net.SplitHostPort(s.Addr)
|
||||||
ret = append(ret, &client{
|
ret = append(ret, &client{
|
||||||
Client: &D.Client{
|
Client: &D.Client{
|
||||||
Net: s.Net,
|
Net: s.Net,
|
||||||
@ -132,10 +133,14 @@ func transform(servers []NameServer) []resolver {
|
|||||||
ClientSessionCache: globalSessionCache,
|
ClientSessionCache: globalSessionCache,
|
||||||
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
||||||
NextProtos: []string{"dns"},
|
NextProtos: []string{"dns"},
|
||||||
|
ServerName: host,
|
||||||
},
|
},
|
||||||
UDPSize: 4096,
|
UDPSize: 4096,
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
},
|
},
|
||||||
Address: s.Addr,
|
port: port,
|
||||||
|
host: host,
|
||||||
|
r: resolver,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
27
go.mod
27
go.mod
@ -1,23 +1,22 @@
|
|||||||
module github.com/Dreamacro/clash
|
module github.com/Dreamacro/clash
|
||||||
|
|
||||||
go 1.13
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
||||||
github.com/eapache/queue v1.1.0 // indirect
|
github.com/eapache/queue v1.1.0 // indirect
|
||||||
github.com/go-chi/chi v4.0.2+incompatible
|
github.com/go-chi/chi v4.1.1+incompatible
|
||||||
github.com/go-chi/cors v1.0.0
|
github.com/go-chi/cors v1.1.1
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible
|
github.com/gofrs/uuid v3.3.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.1
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/miekg/dns v1.1.24
|
github.com/miekg/dns v1.1.29
|
||||||
github.com/oschwald/geoip2-golang v1.3.0
|
github.com/oschwald/geoip2-golang v1.4.0
|
||||||
github.com/oschwald/maxminddb-golang v1.5.0 // indirect
|
github.com/sirupsen/logrus v1.6.0
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/stretchr/testify v1.4.0
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
|
||||||
golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0
|
gopkg.in/eapache/channels.v1 v1.1.0
|
||||||
gopkg.in/yaml.v2 v2.2.7
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
)
|
)
|
||||||
|
78
go.sum
78
go.sum
@ -5,69 +5,61 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
||||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
|
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||||
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||||
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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/miekg/dns v1.1.24 h1:6G8Eop/HM8hpagajbn0rFQvAKZWiiCa8P6N2I07+wwI=
|
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||||
github.com/miekg/dns v1.1.24/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8=
|
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||||
github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
|
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||||
github.com/oschwald/maxminddb-golang v1.5.0 h1:rmyoIV6z2/s9TCJedUuDiKht2RN12LWJ1L7iRGtWY64=
|
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||||
github.com/oschwald/maxminddb-golang v1.5.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||||
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/stretchr/objx v0.1.0/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 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||||
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663 h1:Dd5RoEW+yQi+9DMybroBctIdyiwuNT7sJFMC27/6KxI=
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
|
||||||
golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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 h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/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.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
4
hooks/pre_build
Normal file
4
hooks/pre_build
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Register qemu-*-static for all supported processors except the
|
||||||
|
# current one, but also remove all registered binfmt_misc before
|
||||||
|
docker run --rm --privileged multiarch/qemu-user-static:register --reset --credential yes
|
@ -8,14 +8,16 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
P "github.com/Dreamacro/clash/proxy"
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// forward compatibility before 1.0
|
// forward compatibility before 1.0
|
||||||
@ -83,7 +85,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
updateRules(cfg.Rules)
|
updateRules(cfg.Rules)
|
||||||
updateDNS(cfg.DNS)
|
updateDNS(cfg.DNS)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateExperimental(cfg.Experimental)
|
updateExperimental(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneral() *config.General {
|
func GetGeneral() *config.General {
|
||||||
@ -100,20 +102,30 @@ func GetGeneral() *config.General {
|
|||||||
Authentication: authenticator,
|
Authentication: authenticator,
|
||||||
AllowLan: P.AllowLan(),
|
AllowLan: P.AllowLan(),
|
||||||
BindAddress: P.BindAddress(),
|
BindAddress: P.BindAddress(),
|
||||||
Mode: T.Instance().Mode(),
|
Mode: tunnel.Mode(),
|
||||||
LogLevel: log.Level(),
|
LogLevel: log.Level(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return general
|
return general
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExperimental(c *config.Experimental) {
|
func updateExperimental(c *config.Config) {
|
||||||
T.Instance().UpdateExperimental(c.IgnoreResolveFail)
|
cfg := c.Experimental
|
||||||
|
|
||||||
|
tunnel.UpdateExperimental(cfg.IgnoreResolveFail)
|
||||||
|
if cfg.Interface != "" && c.DNS.Enable {
|
||||||
|
dialer.DialHook = dialer.DialerWithInterface(cfg.Interface)
|
||||||
|
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface)
|
||||||
|
} else {
|
||||||
|
dialer.DialHook = nil
|
||||||
|
dialer.ListenPacketHook = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDNS(c *config.DNS) {
|
func updateDNS(c *config.DNS) {
|
||||||
if c.Enable == false {
|
if c.Enable == false {
|
||||||
dns.DefaultResolver = nil
|
resolver.DefaultResolver = nil
|
||||||
|
tunnel.SetResolver(nil)
|
||||||
dns.ReCreateServer("", nil)
|
dns.ReCreateServer("", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -127,8 +139,10 @@ func updateDNS(c *config.DNS) {
|
|||||||
GeoIP: c.FallbackFilter.GeoIP,
|
GeoIP: c.FallbackFilter.GeoIP,
|
||||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||||
},
|
},
|
||||||
|
Default: c.DefaultNameserver,
|
||||||
})
|
})
|
||||||
dns.DefaultResolver = r
|
resolver.DefaultResolver = r
|
||||||
|
tunnel.SetResolver(r)
|
||||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||||
log.Errorln("Start DNS server error: %s", err.Error())
|
log.Errorln("Start DNS server error: %s", err.Error())
|
||||||
return
|
return
|
||||||
@ -140,28 +154,20 @@ func updateDNS(c *config.DNS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateHosts(tree *trie.Trie) {
|
func updateHosts(tree *trie.Trie) {
|
||||||
dns.DefaultHosts = tree
|
resolver.DefaultHosts = tree
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||||
tunnel := T.Instance()
|
|
||||||
oldProviders := tunnel.Providers()
|
|
||||||
|
|
||||||
// close providers goroutine
|
|
||||||
for _, provider := range oldProviders {
|
|
||||||
provider.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnel.UpdateProxies(proxies, providers)
|
tunnel.UpdateProxies(proxies, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateRules(rules []C.Rule) {
|
func updateRules(rules []C.Rule) {
|
||||||
T.Instance().UpdateRules(rules)
|
tunnel.UpdateRules(rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General) {
|
func updateGeneral(general *config.General) {
|
||||||
log.SetLevel(general.LogLevel)
|
log.SetLevel(general.LogLevel)
|
||||||
T.Instance().SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
|
|
||||||
allowLan := general.AllowLan
|
allowLan := general.AllowLan
|
||||||
P.SetAllowLan(allowLan)
|
P.SetAllowLan(allowLan)
|
||||||
|
27
hub/hub.go
27
hub/hub.go
@ -3,15 +3,40 @@ package hub
|
|||||||
import (
|
import (
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
"github.com/Dreamacro/clash/hub/route"
|
"github.com/Dreamacro/clash/hub/route"
|
||||||
|
"github.com/Dreamacro/clash/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Option func(*config.Config)
|
||||||
|
|
||||||
|
func WithExternalUI(externalUI string) Option {
|
||||||
|
return func(cfg *config.Config) {
|
||||||
|
cfg.General.ExternalUI = externalUI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithExternalController(externalController string) Option {
|
||||||
|
return func(cfg *config.Config) {
|
||||||
|
cfg.General.ExternalController = externalController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSecret(secret string) Option {
|
||||||
|
return func(cfg *config.Config) {
|
||||||
|
cfg.General.Secret = secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse call at the beginning of clash
|
// Parse call at the beginning of clash
|
||||||
func Parse() error {
|
func Parse(options ...Option) error {
|
||||||
cfg, err := executor.Parse()
|
cfg, err := executor.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.General.ExternalUI != "" {
|
if cfg.General.ExternalUI != "" {
|
||||||
route.SetUIPath(cfg.General.ExternalUI)
|
route.SetUIPath(cfg.General.ExternalUI)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
P "github.com/Dreamacro/clash/proxy"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
@ -28,7 +28,7 @@ type configSchema struct {
|
|||||||
RedirPort *int `json:"redir-port"`
|
RedirPort *int `json:"redir-port"`
|
||||||
AllowLan *bool `json:"allow-lan"`
|
AllowLan *bool `json:"allow-lan"`
|
||||||
BindAddress *string `json:"bind-address"`
|
BindAddress *string `json:"bind-address"`
|
||||||
Mode *T.Mode `json:"mode"`
|
Mode *tunnel.TunnelMode `json:"mode"`
|
||||||
LogLevel *log.LogLevel `json:"log-level"`
|
LogLevel *log.LogLevel `json:"log-level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||||
|
|
||||||
if general.Mode != nil {
|
if general.Mode != nil {
|
||||||
T.Instance().SetMode(*general.Mode)
|
tunnel.SetMode(*general.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if general.LogLevel != nil {
|
if general.LogLevel != nil {
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
@ -25,7 +25,7 @@ func proxyProviderRouter() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProviders(w http.ResponseWriter, r *http.Request) {
|
func getProviders(w http.ResponseWriter, r *http.Request) {
|
||||||
providers := T.Instance().Providers()
|
providers := tunnel.Providers()
|
||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"providers": providers,
|
"providers": providers,
|
||||||
})
|
})
|
||||||
@ -63,7 +63,7 @@ func parseProviderName(next http.Handler) http.Handler {
|
|||||||
func findProviderByName(next http.Handler) http.Handler {
|
func findProviderByName(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.Context().Value(CtxKeyProviderName).(string)
|
name := r.Context().Value(CtxKeyProviderName).(string)
|
||||||
providers := T.Instance().Providers()
|
providers := tunnel.Providers()
|
||||||
provider, exist := providers[name]
|
provider, exist := providers[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
render.Status(r, http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
@ -40,7 +40,7 @@ func parseProxyName(next http.Handler) http.Handler {
|
|||||||
func findProxyByName(next http.Handler) http.Handler {
|
func findProxyByName(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||||
proxies := T.Instance().Proxies()
|
proxies := tunnel.Proxies()
|
||||||
proxy, exist := proxies[name]
|
proxy, exist := proxies[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
render.Status(r, http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
@ -54,7 +54,7 @@ func findProxyByName(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
proxies := T.Instance().Proxies()
|
proxies := tunnel.Proxies()
|
||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"proxies": proxies,
|
"proxies": proxies,
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@ package route
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
@ -22,7 +22,7 @@ type Rule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||||
rawRules := T.Instance().Rules()
|
rawRules := tunnel.Rules()
|
||||||
|
|
||||||
rules := []Rule{}
|
rules := []Rule{}
|
||||||
for _, rule := range rawRules {
|
for _, rule := range rawRules {
|
||||||
|
@ -57,10 +57,10 @@ func Start(addr string, secret string) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
r.Use(cors.Handler)
|
r.Use(cors.Handler)
|
||||||
r.Get("/", hello)
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(authentication)
|
r.Use(authentication)
|
||||||
|
|
||||||
|
r.Get("/", hello)
|
||||||
r.Get("/logs", getLogs)
|
r.Get("/logs", getLogs)
|
||||||
r.Get("/traffic", traffic)
|
r.Get("/traffic", traffic)
|
||||||
r.Get("/version", version)
|
r.Get("/version", version)
|
||||||
@ -110,9 +110,9 @@ func authentication(next http.Handler) http.Handler {
|
|||||||
header := r.Header.Get("Authorization")
|
header := r.Header.Get("Authorization")
|
||||||
text := strings.SplitN(header, " ", 2)
|
text := strings.SplitN(header, " ", 2)
|
||||||
|
|
||||||
hasUnvalidHeader := text[0] != "Bearer"
|
hasInvalidHeader := text[0] != "Bearer"
|
||||||
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
|
hasInvalidSecret := len(text) != 2 || text[1] != serverSecret
|
||||||
if hasUnvalidHeader || hasUnvalidSecret {
|
if hasInvalidHeader || hasInvalidSecret {
|
||||||
render.Status(r, http.StatusUnauthorized)
|
render.Status(r, http.StatusUnauthorized)
|
||||||
render.JSON(w, r, ErrUnauthorized)
|
render.JSON(w, r, ErrUnauthorized)
|
||||||
return
|
return
|
||||||
|
@ -55,6 +55,11 @@ func (l LogLevel) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(l.String())
|
return json.Marshal(l.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalYAML serialize LogLevel with yaml
|
||||||
|
func (l LogLevel) MarshalYAML() (interface{}, error) {
|
||||||
|
return l.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l LogLevel) String() string {
|
func (l LogLevel) String() string {
|
||||||
switch l {
|
switch l {
|
||||||
case INFO:
|
case INFO:
|
||||||
|
39
main.go
39
main.go
@ -10,22 +10,38 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/hub"
|
"github.com/Dreamacro/clash/hub"
|
||||||
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
flagset map[string]bool
|
||||||
version bool
|
version bool
|
||||||
|
testConfig bool
|
||||||
homeDir string
|
homeDir string
|
||||||
configFile string
|
configFile string
|
||||||
|
externalUI string
|
||||||
|
externalController string
|
||||||
|
secret string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&homeDir, "d", "", "set configuration directory")
|
flag.StringVar(&homeDir, "d", "", "set configuration directory")
|
||||||
flag.StringVar(&configFile, "f", "", "specify configuration file")
|
flag.StringVar(&configFile, "f", "", "specify configuration file")
|
||||||
|
flag.StringVar(&externalUI, "ext-ui", "", "override external ui directory")
|
||||||
|
flag.StringVar(&externalController, "ext-ctl", "", "override external controller address")
|
||||||
|
flag.StringVar(&secret, "secret", "", "override secret for RESTful API")
|
||||||
flag.BoolVar(&version, "v", false, "show current version of clash")
|
flag.BoolVar(&version, "v", false, "show current version of clash")
|
||||||
|
flag.BoolVar(&testConfig, "t", false, "test configuration and exit")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
flagset = map[string]bool{}
|
||||||
|
flag.Visit(func(f *flag.Flag) {
|
||||||
|
flagset[f.Name] = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -57,7 +73,28 @@ func main() {
|
|||||||
log.Fatalln("Initial configuration directory error: %s", err.Error())
|
log.Fatalln("Initial configuration directory error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := hub.Parse(); err != nil {
|
if testConfig {
|
||||||
|
if _, err := executor.Parse(); err != nil {
|
||||||
|
log.Errorln(err.Error())
|
||||||
|
fmt.Printf("configuration file %s test failed\n", constant.Path.Config())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("configuration file %s test is successful\n", constant.Path.Config())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var options []hub.Option
|
||||||
|
if flagset["ext-ui"] {
|
||||||
|
options = append(options, hub.WithExternalUI(externalUI))
|
||||||
|
}
|
||||||
|
if flagset["ext-ctl"] {
|
||||||
|
options = append(options, hub.WithExternalController(externalController))
|
||||||
|
}
|
||||||
|
if flagset["secret"] {
|
||||||
|
options = append(options, hub.WithSecret(secret))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hub.Parse(options...); err != nil {
|
||||||
log.Fatalln("Parse config error: %s", err.Error())
|
log.Fatalln("Parse config error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,10 +16,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
tun = tunnel.Instance()
|
|
||||||
)
|
|
||||||
|
|
||||||
type HttpListener struct {
|
type HttpListener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
@ -100,9 +96,9 @@ func handleConn(conn net.Conn, cache *cache.Cache) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tun.Add(adapters.NewHTTPS(request, conn))
|
tunnel.Add(adapters.NewHTTPS(request, conn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tun.Add(adapters.NewHTTP(request, conn))
|
tunnel.Add(adapters.NewHTTP(request, conn))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/proxy/http"
|
"github.com/Dreamacro/clash/proxy/http"
|
||||||
"github.com/Dreamacro/clash/proxy/redir"
|
"github.com/Dreamacro/clash/proxy/redir"
|
||||||
"github.com/Dreamacro/clash/proxy/socks"
|
"github.com/Dreamacro/clash/proxy/socks"
|
||||||
@ -18,6 +19,7 @@ var (
|
|||||||
socksUDPListener *socks.SockUDPListener
|
socksUDPListener *socks.SockUDPListener
|
||||||
httpListener *http.HttpListener
|
httpListener *http.HttpListener
|
||||||
redirListener *redir.RedirListener
|
redirListener *redir.RedirListener
|
||||||
|
redirUDPListener *redir.RedirUDPListener
|
||||||
)
|
)
|
||||||
|
|
||||||
type listener interface {
|
type listener interface {
|
||||||
@ -131,6 +133,14 @@ func ReCreateRedir(port int) error {
|
|||||||
redirListener = nil
|
redirListener = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if redirUDPListener != nil {
|
||||||
|
if redirUDPListener.Address() == addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
redirUDPListener.Close()
|
||||||
|
redirUDPListener = nil
|
||||||
|
}
|
||||||
|
|
||||||
if portIsZero(addr) {
|
if portIsZero(addr) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -141,6 +151,11 @@ func ReCreateRedir(port int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirUDPListener, err = redir.NewRedirUDPProxy(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("Failed to start Redir UDP Listener: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,10 +9,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
tun = tunnel.Instance()
|
|
||||||
)
|
|
||||||
|
|
||||||
type RedirListener struct {
|
type RedirListener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
@ -59,5 +55,5 @@ func handleRedir(conn net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
tun.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP))
|
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR))
|
||||||
}
|
}
|
||||||
|
74
proxy/redir/udp.go
Normal file
74
proxy/redir/udp.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
err = setsockopt(c, 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.REDIR))
|
||||||
|
}
|
73
proxy/redir/udp_linux.go
Normal file
73
proxy/redir/udp_linux.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPV6_TRANSPARENT = 0x4b
|
||||||
|
IPV6_RECVORIGDSTADDR = 0x4a
|
||||||
|
)
|
||||||
|
|
||||||
|
func setsockopt(c *net.UDPConn, addr string) error {
|
||||||
|
isIPv6 := true
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip != nil && ip.To4() != nil {
|
||||||
|
isIPv6 = false
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := c.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.Control(func(fd uintptr) {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
|
}
|
||||||
|
if err == nil && isIPv6 {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||||
|
}
|
||||||
|
if err == nil && isIPv6 {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
16
proxy/redir/udp_other.go
Normal file
16
proxy/redir/udp_other.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setsockopt(c *net.UDPConn, addr string) error {
|
||||||
|
return errors.New("UDP redir not supported on current platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
|
}
|
38
proxy/redir/utils.go
Normal file
38
proxy/redir/utils.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
lAddr *net.UDPAddr
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Data() []byte {
|
||||||
|
return c.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBack opens a new socket binding `addr` to wirte UDP packet back
|
||||||
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr)
|
||||||
|
if err != nil {
|
||||||
|
n = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = tc.Write(b)
|
||||||
|
tc.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the source IP/Port of UDP Packet
|
||||||
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
|
return c.lAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Drop() {
|
||||||
|
pool.Put(c.buf)
|
||||||
|
return
|
||||||
|
}
|
96
proxy/redir/utils_linux.go
Normal file
96
proxy/redir/utils_linux.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lSockAddr, err := udpAddrToSockAddr(lAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String()))
|
||||||
|
defer fdFile.Close()
|
||||||
|
|
||||||
|
c, err := net.FileConn(fdFile)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.(*net.UDPConn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
|
||||||
|
switch {
|
||||||
|
case addr.IP.To4() != nil:
|
||||||
|
ip := [4]byte{}
|
||||||
|
copy(ip[:], addr.IP.To4())
|
||||||
|
|
||||||
|
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
ip := [16]byte{}
|
||||||
|
copy(ip[:], addr.IP.To16())
|
||||||
|
|
||||||
|
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
zoneID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
|
||||||
|
switch net[len(net)-1] {
|
||||||
|
case '4':
|
||||||
|
return syscall.AF_INET
|
||||||
|
case '6':
|
||||||
|
return syscall.AF_INET6
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
|
||||||
|
return syscall.AF_INET
|
||||||
|
}
|
||||||
|
return syscall.AF_INET6
|
||||||
|
}
|
12
proxy/redir/utils_other.go
Normal file
12
proxy/redir/utils_other.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package redir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
|
}
|
@ -13,10 +13,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
tun = tunnel.Instance()
|
|
||||||
)
|
|
||||||
|
|
||||||
type SockListener struct {
|
type SockListener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
@ -68,5 +64,5 @@ func handleSocks(conn net.Conn) {
|
|||||||
io.Copy(ioutil.Discard, conn)
|
io.Copy(ioutil.Discard, conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
|
tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS))
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,10 @@ import (
|
|||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SockUDPListener struct {
|
type SockUDPListener struct {
|
||||||
@ -21,13 +23,18 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
sl := &SockUDPListener{l, addr, false}
|
sl := &SockUDPListener{l, addr, false}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
n, remoteAddr, err := l.ReadFrom(buf)
|
n, remoteAddr, err := l.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
if sl.closed {
|
if sl.closed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -53,15 +60,14 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
|
|||||||
target, payload, err := socks5.DecodeUDPPacket(buf)
|
target, payload, err := socks5.DecodeUDPPacket(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unresolved UDP packet, return buffer to the pool
|
// Unresolved UDP packet, return buffer to the pool
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
packet := &fakeConn{
|
packet := &packet{
|
||||||
PacketConn: pc,
|
pc: pc,
|
||||||
remoteAddr: addr,
|
rAddr: addr,
|
||||||
targetAddr: target,
|
|
||||||
payload: payload,
|
payload: payload,
|
||||||
bufRef: buf,
|
bufRef: buf,
|
||||||
}
|
}
|
||||||
tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS, C.UDP))
|
tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS))
|
||||||
}
|
}
|
||||||
|
@ -7,39 +7,32 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeConn struct {
|
type packet struct {
|
||||||
net.PacketConn
|
pc net.PacketConn
|
||||||
remoteAddr net.Addr
|
rAddr net.Addr
|
||||||
targetAddr socks5.Addr
|
|
||||||
payload []byte
|
payload []byte
|
||||||
bufRef []byte
|
bufRef []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeConn) Data() []byte {
|
func (c *packet) Data() []byte {
|
||||||
return c.payload
|
return c.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||||
func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
from := c.targetAddr
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||||
if addr != nil {
|
|
||||||
// if addr is provided, use the parsed addr
|
|
||||||
from = socks5.ParseAddrToSocksAddr(addr)
|
|
||||||
}
|
|
||||||
packet, err := socks5.EncodeUDPPacket(from, b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.PacketConn.WriteTo(packet, c.remoteAddr)
|
return c.pc.WriteTo(packet, c.rAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalAddr returns the source IP/Port of UDP Packet
|
// LocalAddr returns the source IP/Port of UDP Packet
|
||||||
func (c *fakeConn) LocalAddr() net.Addr {
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
return c.remoteAddr
|
return c.rAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeConn) Close() error {
|
func (c *packet) Drop() {
|
||||||
err := c.PacketConn.Close()
|
pool.Put(c.bufRef)
|
||||||
pool.BufPool.Put(c.bufRef[:cap(c.bufRef)])
|
return
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"github.com/Dreamacro/clash/component/mmdb"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"github.com/oschwald/geoip2-golang"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mmdb *geoip2.Reader
|
|
||||||
once sync.Once
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GEOIP struct {
|
type GEOIP struct {
|
||||||
@ -29,7 +20,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool {
|
|||||||
if ip == nil {
|
if ip == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
record, _ := mmdb.Country(ip)
|
record, _ := mmdb.Instance().Country(ip)
|
||||||
return record.Country.IsoCode == g.country
|
return record.Country.IsoCode == g.country
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,14 +37,6 @@ func (g *GEOIP) NoResolveIP() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP {
|
func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP {
|
||||||
once.Do(func() {
|
|
||||||
var err error
|
|
||||||
mmdb, err = geoip2.Open(C.Path.MMDB())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Can't load mmdb: %s", err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
geoip := &GEOIP{
|
geoip := &GEOIP{
|
||||||
country: country,
|
country: country,
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
|
@ -14,12 +14,12 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||||
req := request.R
|
req := request.R
|
||||||
host := req.Host
|
host := req.Host
|
||||||
|
|
||||||
inboundReeder := bufio.NewReader(request)
|
inboundReader := bufio.NewReader(request)
|
||||||
outboundReeder := bufio.NewReader(outbound)
|
outboundReader := bufio.NewReader(outbound)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
|
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
|
||||||
@ -33,7 +33,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleResponse:
|
handleResponse:
|
||||||
resp, err := http.ReadResponse(outboundReeder, req)
|
resp, err := http.ReadResponse(outboundReader, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -61,14 +61,14 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// even if resp.Write write body to the connection, but some http request have to Copy to close it
|
// even if resp.Write write body to the connection, but some http request have to Copy to close it
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
_, err = io.CopyBuffer(request, resp.Body, buf)
|
_, err = io.CopyBuffer(request, resp.Body, buf)
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err = http.ReadRequest(inboundReeder)
|
req, err = http.ReadRequest(inboundReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -81,38 +81,39 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) {
|
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
|
||||||
if _, err := pc.WriteTo(packet.Data(), addr); err != nil {
|
defer packet.Drop()
|
||||||
|
|
||||||
|
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DefaultManager.Upload() <- int64(len(packet.Data()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, omitSrcAddr bool, timeout time.Duration) {
|
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
defer pool.BufPool.Put(buf[:cap(buf)])
|
defer pool.Put(buf)
|
||||||
defer t.natTable.Delete(key)
|
defer natTable.Delete(key)
|
||||||
defer pc.Close()
|
defer pc.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
pc.SetReadDeadline(time.Now().Add(timeout))
|
pc.SetReadDeadline(time.Now().Add(udpTimeout))
|
||||||
n, from, err := pc.ReadFrom(buf)
|
n, from, err := pc.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if from != nil && omitSrcAddr {
|
|
||||||
from = nil
|
if fAddr != nil {
|
||||||
|
from = fAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = packet.WriteBack(buf[:n], from)
|
n, err = packet.WriteBack(buf[:n], from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DefaultManager.Download() <- int64(n)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
||||||
relay(request, outbound)
|
relay(request, outbound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,16 +122,16 @@ func relay(leftConn, rightConn net.Conn) {
|
|||||||
ch := make(chan error)
|
ch := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
_, err := io.CopyBuffer(leftConn, rightConn, buf)
|
_, err := io.CopyBuffer(leftConn, rightConn, buf)
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
leftConn.SetReadDeadline(time.Now())
|
leftConn.SetReadDeadline(time.Now())
|
||||||
ch <- err
|
ch <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
buf := pool.BufPool.Get().([]byte)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
io.CopyBuffer(rightConn, leftConn, buf)
|
io.CopyBuffer(rightConn, leftConn, buf)
|
||||||
pool.BufPool.Put(buf[:cap(buf)])
|
pool.Put(buf)
|
||||||
rightConn.SetReadDeadline(time.Now())
|
rightConn.SetReadDeadline(time.Now())
|
||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,15 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ResetStatistic() {
|
||||||
|
m.uploadTemp = 0
|
||||||
|
m.uploadBlip = 0
|
||||||
|
m.uploadTotal = 0
|
||||||
|
m.downloadTemp = 0
|
||||||
|
m.downloadBlip = 0
|
||||||
|
m.downloadTotal = 0
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) handle() {
|
func (m *Manager) handle() {
|
||||||
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
|
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
|
||||||
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)
|
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user