Compare commits

...

98 Commits

Author SHA1 Message Date
243d8a2844 Chore: update README.md 2019-05-09 21:05:47 +08:00
225c530d13 Feature: add DST-PORT and SRC-PORT 2019-05-09 21:00:29 +08:00
cff4841f3e Chore: fix socks reader overflow & update dependencies 2019-05-06 21:00:29 +08:00
f352f4479e Feature: support fakeip 2019-05-03 00:05:14 +08:00
762f227512 Feature: support vmess udp 2019-04-25 16:32:15 +08:00
936ea3aa55 Feature: support outbound socks5 udp 2019-04-25 13:48:47 +08:00
cec2206774 Feature: add experimental config for resolving ip fail behavior 2019-04-24 12:02:52 +08:00
90e3dccacd Fix: add missing error check 2019-04-24 10:29:29 +08:00
c92cda6980 Feature: socks5 udp associate 2019-04-23 23:30:13 +08:00
49f8902961 Fix: typo in initial config file (#166) 2019-04-22 09:57:08 +08:00
7770e18430 Chore: add GitBook link to README.md (#155) 2019-04-15 19:07:18 +08:00
593a63c228 Fix: make releases script 2019-03-30 15:34:24 +08:00
744728cb84 Chore: update README.md 2019-03-30 14:20:04 +08:00
2036f8cb7a Fix: IP-CIDR invalid payload crash 2019-03-30 14:11:59 +08:00
531f487629 Fix: incorrect mutex in speedTest (#153) 2019-03-29 10:27:26 +08:00
18f885a92a Feature: add interval url test for load-balance 2019-03-28 19:00:41 +08:00
d3b280a7e5 Fix: reuse Current.HomeDir until go 1.13 release 2019-03-28 18:20:19 +08:00
d1f6886558 Style: use atomic CompareAndSwap (#151) 2019-03-26 23:48:03 +08:00
791d72e05b Fix: crash when key value is nil 2019-03-25 20:42:20 +08:00
14600a8170 Fix: dns hot reload no effect 2019-03-23 19:41:41 +08:00
bb267e4a1f Feature: add version command (#148) 2019-03-23 19:24:26 +08:00
f99da37168 Fix: fallback & url-test lose efficacy 2019-03-23 16:29:27 +08:00
7a9d986ff3 Feature: add delay history and improve url-test behavior 2019-03-17 14:52:39 +08:00
63446da5fa Fix: expand UDPSize to avoid resolving error (#139) 2019-03-17 14:08:15 +08:00
acf55a7f64 Improve: Dial would reset proxy alive status 2019-03-16 00:43:16 +08:00
8c608f5d7a Feature: add custom headers support in v2ray-plugin (#137) 2019-03-15 09:43:46 +08:00
7f0c7d7802 Fix: should not return extra ip in msgToIP 2019-03-03 17:23:59 +08:00
7683271fe6 Style: rename Generator with Dial 2019-03-03 11:59:07 +08:00
23bb01a4df Fix: http request keepAlive with right http header 2019-03-03 11:51:15 +08:00
0011c7acfe Improve: support tcp dns server & return an error when parsing nameserver (#127) 2019-03-01 00:52:30 +08:00
d75f9ff783 Migration: go 1.12 2019-02-27 01:02:43 +08:00
815e80f720 Fix: dns use Extra records 2019-02-24 01:26:51 +08:00
ca5399a16e Fix: dns cache behavior 2019-02-23 20:31:59 +08:00
04927229ff Fix: disconnect normal proxy request 2019-02-21 16:16:49 +08:00
c0bd82d62b Chore: rename final 2019-02-18 21:53:57 +08:00
5c8bb24121 Fix: crash when directly request proxy server 2019-02-18 20:14:18 +08:00
575720e0cc Fix: windows 386 build 2019-02-15 23:43:28 +08:00
e7997a035b Chore: update README.md 2019-02-15 22:01:11 +08:00
287ad5bc53 Fix: vmess handshake block (#117) 2019-02-15 21:55:15 +08:00
c295c5e412 Feature: add load-balance group 2019-02-15 14:25:20 +08:00
8636a4f589 Fix: return 502 in http outbound (#116) 2019-02-14 11:37:47 +08:00
7a0717830c Fix: api invalid returning 2019-02-13 23:45:43 +08:00
5920b05752 Fix: crash when ip is nil 2019-02-12 12:29:33 +08:00
26a87f9d34 Fix: redir-host mode crash 2019-02-11 17:20:42 +08:00
8da19e81a4 Update: README.md 2019-02-11 15:55:17 +08:00
1339487ce4 Fix: tun2socks not lookup IP 2019-02-11 15:44:42 +08:00
2383cca2ce Feature: add v2ray-plugin 2019-02-11 15:25:39 +08:00
53b5ef199f Fix: parse proxies shadow variable 2019-02-04 09:39:17 +08:00
754df5ba9b Chore: update dependencies 2019-02-02 21:53:30 +08:00
1016355ef6 Chore: log dns server address when success 2019-02-02 21:37:36 +08:00
b594cbc68d Fix: parse ip string when use socks proxy (#100) 2019-02-02 21:11:27 +08:00
42d33fe629 Feature: SOURCE-IP-CIDR rule type (#96) 2019-02-02 21:03:13 +08:00
bfe51e46b0 Improve: lazy resolve ip 2019-02-02 20:47:38 +08:00
e30a628702 Fix: shadow variable 2019-01-30 20:22:52 +08:00
5581698908 Chore: improve programming style (#109) 2019-01-29 23:46:18 +08:00
36b5d1f18f Fix: DNS server returns the correct TTL 2019-01-25 15:38:14 +08:00
bd6c6a9ad1 Chore: print origin rule when format error (#92) 2019-01-14 10:35:11 +08:00
83fac44010 Fix: nghttpx return 400 error (#84) 2019-01-07 10:47:25 +08:00
15a77fa71b Fix: print log when start dns server failed (#87) 2019-01-06 14:31:42 +08:00
7768c5b933 Chore: add more platform release (#83) 2019-01-03 10:49:09 +08:00
4e91118a05 Feature: add freebsd release (#80)
add freebsd support
2018-12-31 20:57:21 +08:00
7b5e1f759c Fix: authentication with stream api 2018-12-29 14:11:54 +08:00
3f6c707aa9 Fix: patch config field 2018-12-23 20:25:49 +08:00
532ec88964 Chore: make a consistent code style 2018-12-23 00:42:08 +08:00
cb118d4371 Chore: improve outbound architecture 2018-12-22 23:56:42 +08:00
a7cfc81885 Fix: ignore some general configuration 2018-12-21 22:51:37 +08:00
551ab68c1e Fix: allow access to external-ui without authentication (#75) 2018-12-21 17:48:29 +08:00
ef6260282f Fix: parse external-ui 2018-12-21 10:55:21 +08:00
49635eab6c Chore: update external-ui explanation 2018-12-20 22:34:38 +08:00
a46041b81c Fix: force param make no sense 2018-12-20 22:23:31 +08:00
a6bbc67afb Feature: add custom ui support in API 2018-12-20 01:29:13 +08:00
afc4644dd1 Feature: FreeBSD compatibility patch (#63) 2018-12-18 10:37:00 +08:00
1607d3253f Feature: add websocket headers support in vmess 2018-12-11 00:25:05 +08:00
34c8655974 Fix: don't keepalive when connection is close (#65)
fixed #60
2018-12-10 11:48:57 +08:00
5e4b35e03a Chore: standardize API returns 2018-12-10 11:33:37 +08:00
fa9077969c Fix: dns crash & remove unused debug log 2018-12-10 11:00:52 +08:00
fcb1a7813a Fix: dns msg to ip 2018-12-06 13:29:43 +08:00
6f1bc3d65b Fix: add PATCH for CORS 2018-12-06 10:54:45 +08:00
2b93c9d4c9 Fix: resolve ip crash 2018-12-06 10:51:37 +08:00
f93d6aa294 Fix: crash when dns not set 2018-12-05 21:52:31 +08:00
f192d591c7 Chore: bump to 0.10.0 2018-12-05 21:26:04 +08:00
03c249ecb1 Feature: add custom DNS support (#56) 2018-12-05 21:13:29 +08:00
da5db36ccf Fix: policy group unexpectedly closed 2018-12-05 18:19:30 +08:00
ca6e67a384 Feature: add silent info level 2018-12-03 23:41:40 +08:00
6636db242b Feature: add http/https [connect] proxy (#52) 2018-12-03 23:27:00 +08:00
f5715c4f42 Fix: chunk size limit in tls obfs (#54)
* Fix: add chunkSize limit in TLSObfs

* Chore: add length var for len(b)
2018-12-01 09:32:02 +08:00
9cfd26d440 Feat: add switch config file API 2018-11-30 17:42:40 +08:00
dc24dd4d89 Fix: tls server name missing in vmess 2018-11-28 23:24:57 +08:00
a64cea5011 Fix: patch config API 2018-11-28 10:38:30 +08:00
f6743d4d21 Fix: chrome crash when using SwitchyOmega by reject rule (#47)
* Fix: chrome crash when using SwitchyOmega by reject rule
2018-11-25 17:00:11 +08:00
970643b144 Fix: goroutine leak while closing proxy (#43)
* Fix: goroutine leak while closing proxy

* Chore: improve proxy architecture

* Fix: stack overflow
2018-11-22 11:54:01 +08:00
05bf4d44ab Change: replace FINAL with MATCH in a progressive way 2018-11-21 18:21:24 +08:00
c7a349e1fe Improve: auto change payload to lowercase 2018-11-21 13:59:39 +08:00
01a477bd3d Chore: improve code architecture 2018-11-21 13:47:46 +08:00
91e35f2f6a Fix: resolve path in windows 2018-11-14 20:58:10 +08:00
b0e062dc7c Feature: SOCKS5 authentication support (#34)
* Feature: socks5 auth support

* Chore: make code unified

* Fix: auth buffer length
2018-11-09 17:36:30 +08:00
09cd34ec07 Chore: update README.md 2018-11-08 20:14:57 +08:00
da391356dd Fix: simple-obfs tls 2018-11-07 16:57:21 +08:00
92 changed files with 4578 additions and 1750 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
bin/*
# Test binary, build with `go test -c` # Test binary, build with `go test -c`
*.test *.test

View File

@ -1,7 +1,7 @@
language: go language: go
sudo: false sudo: false
go: go:
- "1.11" - '1.12'
install: install:
- "go mod download" - "go mod download"
env: env:

View File

@ -1,23 +1,93 @@
NAME=clash NAME=clash
BINDIR=bin BINDIR=bin
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' VERSION=$(shell git describe --tags || echo "unkown version")
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s'
all: linux macos win64 PLATFORM_LIST = \
darwin-amd64 \
linux-386 \
linux-amd64 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle \
linux-mips64 \
linux-mips64le \
freebsd-386 \
freebsd-amd64
linux: WINDOWS_ARCH_LIST = \
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ windows-386 \
windows-amd64
macos: all: linux-amd64 darwin-amd64 windows-amd64 # Most used
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
win64: linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv6:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-hardfloat:
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle:
GOARCH=mipsle GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64:
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
releases: linux macos win64 gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
chmod +x $(BINDIR)/$(NAME)-* zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
gzip $(BINDIR)/$(NAME)-linux
gzip $(BINDIR)/$(NAME)-macos
zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe
$(gz_releases): %.gz : %
chmod +x $(BINDIR)/$(NAME)-$(basename $@)
gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@)
$(zip_releases): %.zip : %
zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases)
clean: clean:
rm $(BINDIR)/* rm $(BINDIR)/*

121
README.md
View File

@ -1,11 +1,9 @@
<h1 align="center"> <h1 align="center">
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200"> <img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200">
<br> <br>Clash<br>
Clash
<br>
</h1> </h1>
<h4 align="center">A rule based proxy in Go.</h4> <h4 align="center">A rule-based tunnel in Go.</h4>
<p align="center"> <p align="center">
<a href="https://travis-ci.org/Dreamacro/clash"> <a href="https://travis-ci.org/Dreamacro/clash">
@ -13,7 +11,7 @@
alt="Travis-CI"> alt="Travis-CI">
</a> </a>
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash"> <a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square"> <img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
</a> </a>
<a href="https://github.com/Dreamacro/clash/releases"> <a href="https://github.com/Dreamacro/clash/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square"> <img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
@ -22,16 +20,12 @@
## Features ## Features
- HTTP/HTTPS and SOCKS proxy - HTTP/HTTPS and SOCKS protocol
- Surge like configuration - Surge like configuration
- GeoIP rule support - GeoIP rule support
- Support Vmess/Shadowsocks/Socks5 - Support Vmess/Shadowsocks/Socks5
- Support for Netfilter TCP redirect - Support for Netfilter TCP redirect
## Discussion
[Telegram Group](https://t.me/clash_discuss)
## Install ## Install
You can build from source: You can build from source:
@ -42,7 +36,13 @@ go get -u -v github.com/Dreamacro/clash
Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases) Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases)
Requires Go >= 1.10. Requires Go >= 1.12.
Checkout Clash version:
```sh
clash -v
```
## Daemon ## Daemon
@ -62,8 +62,6 @@ If you have Docker installed, you can run clash directly using `docker-compose`.
## Config ## Config
**NOTE: after v0.8.0, clash using yaml as configuration file**
The default configuration directory is `$HOME/.config/clash` The default configuration directory is `$HOME/.config/clash`
The name of the configuration file is `config.yml` The name of the configuration file is `config.yml`
@ -85,7 +83,7 @@ port: 7890
# port of SOCKS5 # port of SOCKS5
socks-port: 7891 socks-port: 7891
# redir proxy for Linux and macOS # redir port for Linux and macOS
# redir-port: 7892 # redir-port: 7892
allow-lan: false allow-lan: false
@ -94,23 +92,70 @@ allow-lan: false
mode: Rule mode: Rule
# set log level to stdout (default is info) # set log level to stdout (default is info)
# info / warning / error / debug # info / warning / error / debug / silent
log-level: info log-level: info
# A RESTful API for clash # A RESTful API for clash
external-controller: 127.0.0.1:9090 external-controller: 127.0.0.1:9090
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
# input is a relative path to the configuration directory or an absolute path
# external-ui: folder
# Secret for RESTful API (Optional) # Secret for RESTful API (Optional)
# secret: "" # secret: ""
# experimental feature
experimental:
ignore-resolve-fail: true # ignore dns reslove fail, default value is true
# dns:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# listen: 0.0.0.0:53
# 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
# nameserver:
# - 114.114.114.114
# - tls://dns.rubyfish.cn:853 # dns over tls
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
# - tcp://1.1.1.1
Proxy: Proxy:
# shadowsocks # shadowsocks
# The types of cipher are consistent with go-shadowsocks2 # The types of cipher are consistent with go-shadowsocks2
# support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20
# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 # In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305
- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } - { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", udp: true }
- { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com }
# old obfs configuration remove after prerelease
- name: "ss2"
type: ss
server: server
port: 443
cipher: AEAD_CHACHA20_POLY1305
password: "password"
plugin: obfs
plugin-opts:
mode: tls # or http
# host: bing.com
- name: "ss3"
type: ss
server: server
port: 443
cipher: AEAD_CHACHA20_POLY1305
password: "password"
plugin: v2ray-plugin
plugin-opts:
mode: websocket # no QUIC now
# tls: true # wss
# skip-cert-verify: true
# host: bing.com
# path: "/"
# headers:
# custom: value
# vmess # vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none # cipher support auto/aes-128-gcm/chacha20-poly1305/none
@ -119,40 +164,62 @@ Proxy:
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
# with tls and skip-cert-verify # with tls and skip-cert-verify
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
# with ws # with ws-path and ws-headers
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } }
# with ws + tls # with ws + tls
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
# socks5 # socks5
- { name: "socks", type: socks5, server: server, port: 443 } - { name: "socks", type: socks5, server: server, port: 443 }
# socks5 with authentication
- { name: "socks", type: socks5, server: server, port: 443, username: "username", password: "password" }
# with tls # with tls
- { name: "socks", type: socks5, server: server, port: 443, tls: true } - { name: "socks", type: socks5, server: server, port: 443, tls: true }
# with tls and skip-cert-verify # with tls and skip-cert-verify
- { name: "socks", type: socks5, server: server, port: 443, tls: true, skip-cert-verify: true } - { name: "socks", type: socks5, server: server, port: 443, tls: true, skip-cert-verify: true }
# http
- { name: "http", type: http, server: server, port: 443 }
# http with authentication
- { name: "http", type: http, server: server, port: 443, username: "username", password: "password" }
# with tls (https)
- { name: "http", type: http, server: server, port: 443, tls: true }
# with tls (https) and skip-cert-verify
- { name: "http", type: http, server: server, port: 443, tls: true, skip-cert-verify: true }
Proxy Group: Proxy Group:
# 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", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } - { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } - { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# load-balance: The request of the same eTLD will be dial on the same proxy.
- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# select is used for selecting proxy or proxy group # select is used for selecting proxy or proxy group
# you can use RESTful API to switch proxy, is recommended for use in GUI. # you can use RESTful API to switch proxy, is recommended for use in GUI.
- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] } - { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] }
Rule: Rule:
- DOMAIN-SUFFIX,google.com,Proxy - DOMAIN-SUFFIX,google.com,auto
- DOMAIN-KEYWORD,google,Proxy - DOMAIN-KEYWORD,google,auto
- DOMAIN,google.com,Proxy - DOMAIN,google.com,auto
- DOMAIN-SUFFIX,ad.com,REJECT - DOMAIN-SUFFIX,ad.com,REJECT
- IP-CIDR,127.0.0.0/8,DIRECT - IP-CIDR,127.0.0.0/8,DIRECT
# rename SOURCE-IP-CIDR and would remove after prerelease
- SRC-IP-CIDR,192.168.1.201/32,DIRECT
- GEOIP,CN,DIRECT - GEOIP,CN,DIRECT
# note: there is two "," - DST-PORT,80,DIRECT
- FINAL,,Proxy - SRC-PORT,7777,DIRECT
# FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
- MATCH,auto
``` ```
## Documentations
https://clash.gitbook.io/
## Thanks ## Thanks
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
@ -167,5 +234,5 @@ Rule:
- [x] Complementing the necessary rule operators - [x] Complementing the necessary rule operators
- [x] Redir proxy - [x] Redir proxy
- [ ] UDP support - [x] UDP support
- [ ] Connection manager - [ ] Connection manager

View File

@ -10,32 +10,27 @@ import (
// HTTPAdapter is a adapter for HTTP connection // HTTPAdapter is a adapter for HTTP connection
type HTTPAdapter struct { type HTTPAdapter struct {
net.Conn
metadata *C.Metadata metadata *C.Metadata
conn net.Conn
R *http.Request R *http.Request
} }
// Close HTTP connection
func (h *HTTPAdapter) Close() {
h.conn.Close()
}
// Metadata return destination metadata // Metadata return destination metadata
func (h *HTTPAdapter) Metadata() *C.Metadata { func (h *HTTPAdapter) Metadata() *C.Metadata {
return h.metadata return h.metadata
} }
// Conn return raw net.Conn of HTTP
func (h *HTTPAdapter) Conn() net.Conn {
return h.conn
}
// NewHTTP is HTTPAdapter generator // NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
metadata := parseHTTPAddr(request)
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &HTTPAdapter{ return &HTTPAdapter{
metadata: parseHTTPAddr(request), metadata: metadata,
R: request, R: request,
conn: conn, Conn: conn,
} }
} }

View File

@ -7,8 +7,13 @@ import (
// NewHTTPS is HTTPAdapter generator // NewHTTPS is HTTPAdapter generator
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
metadata := parseHTTPAddr(request)
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{ return &SocketAdapter{
metadata: parseHTTPAddr(request), metadata: metadata,
conn: conn, Conn: conn,
} }
} }

View File

@ -3,35 +3,33 @@ package adapters
import ( import (
"net" "net"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/socks"
) )
// SocketAdapter is a adapter for socks and redir connection // SocketAdapter is a adapter for socks and redir connection
type SocketAdapter struct { type SocketAdapter struct {
conn net.Conn net.Conn
metadata *C.Metadata metadata *C.Metadata
} }
// Close socks and redir connection
func (s *SocketAdapter) Close() {
s.conn.Close()
}
// Metadata return destination metadata // Metadata return destination metadata
func (s *SocketAdapter) Metadata() *C.Metadata { func (s *SocketAdapter) Metadata() *C.Metadata {
return s.metadata return s.metadata
} }
// Conn return raw net.Conn
func (s *SocketAdapter) Conn() net.Conn {
return s.conn
}
// NewSocket is SocketAdapter generator // NewSocket is SocketAdapter generator
func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter { func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, netType C.NetWork) *SocketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{ return &SocketAdapter{
conn: conn, Conn: conn,
metadata: parseSocksAddr(target), metadata: metadata,
} }
} }

View File

@ -5,37 +5,30 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/socks"
) )
func parseSocksAddr(target socks.Addr) *C.Metadata { func parseSocksAddr(target socks5.Addr) *C.Metadata {
var host, port string metadata := &C.Metadata{
var ip net.IP AddrType: int(target[0]),
}
switch target[0] { switch target[0] {
case socks.AtypDomainName: case socks5.AtypDomainName:
host = string(target[2 : 2+target[1]]) metadata.Host = string(target[2 : 2+target[1]])
port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
ipAddr, err := net.ResolveIPAddr("ip", host) case socks5.AtypIPv4:
if err == nil { ip := net.IP(target[1 : 1+net.IPv4len])
ip = ipAddr.IP metadata.DstIP = &ip
} metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks.AtypIPv4: case socks5.AtypIPv6:
ip = net.IP(target[1 : 1+net.IPv4len]) ip := net.IP(target[1 : 1+net.IPv6len])
port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) metadata.DstIP = &ip
case socks.AtypIPv6: metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
ip = net.IP(target[1 : 1+net.IPv6len])
port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
} }
return &C.Metadata{ return metadata
NetWork: C.TCP,
AddrType: int(target[0]),
Host: host,
IP: &ip,
Port: port,
}
} }
func parseHTTPAddr(request *http.Request) *C.Metadata { func parseHTTPAddr(request *http.Request) *C.Metadata {
@ -44,28 +37,36 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
if port == "" { if port == "" {
port = "80" port = "80"
} }
ipAddr, err := net.ResolveIPAddr("ip", host)
var resolveIP *net.IP
if err == nil {
resolveIP = &ipAddr.IP
}
var addType int metadata := &C.Metadata{
ip := net.ParseIP(host)
switch {
case ip == nil:
addType = socks.AtypDomainName
case ip.To4() == nil:
addType = socks.AtypIPv6
default:
addType = socks.AtypIPv4
}
return &C.Metadata{
NetWork: C.TCP, NetWork: C.TCP,
AddrType: addType, Type: C.HTTP,
AddrType: C.AtypDomainName,
Host: host, Host: host,
IP: resolveIP, DstIP: nil,
Port: port, DstPort: port,
} }
ip := net.ParseIP(host)
if ip != nil {
switch {
case ip.To4() == nil:
metadata.AddrType = C.AtypIPv6
default:
metadata.AddrType = C.AtypIPv4
}
metadata.DstIP = &ip
}
return metadata
}
func parseAddr(addr string) (*net.IP, string, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, "", err
}
ip := net.ParseIP(host)
return &ip, port, nil
} }

148
adapters/outbound/base.go Normal file
View File

@ -0,0 +1,148 @@
package adapters
import (
"encoding/json"
"errors"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant"
)
type Base struct {
name string
tp C.AdapterType
udp bool
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return nil, nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) Destroy() {}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
alive bool
}
func (p *Proxy) Alive() bool {
return p.alive
}
func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) {
conn, err := p.ProxyAdapter.Dial(metadata)
if err != nil {
p.alive = false
}
return conn, err
}
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
}
return histories
}
// LastDelay return last history record. if proxy is not alive, return the max value of int16.
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive {
return max
}
head := p.history.First()
if head == nil {
return max
}
history := head.(C.DelayHistory)
if history.Delay == 0 {
return max
}
return history.Delay
}
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]interface{}{}
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
func (p *Proxy) URLTest(url string) (t uint16, err error) {
defer func() {
p.alive = err == nil
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > 10 {
p.history.Pop()
}
}()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.Dial(&addr)
if err != nil {
return
}
defer instance.Close()
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Get(url)
if err != nil {
return
}
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), true}
}

View File

@ -6,40 +6,43 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// DirectAdapter is a directly connected adapter type Direct struct {
type DirectAdapter struct { *Base
conn net.Conn
} }
// Close is used to close connection func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) {
func (d *DirectAdapter) Close() { address := net.JoinHostPort(metadata.Host, metadata.DstPort)
d.conn.Close() if metadata.DstIP != nil {
} address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
}
// Conn is used to http request c, err := net.DialTimeout("tcp", address, tcpTimeout)
func (d *DirectAdapter) Conn() net.Conn {
return d.conn
}
type Direct struct{}
func (d *Direct) Name() string {
return "DIRECT"
}
func (d *Direct) Type() C.AdapterType {
return C.Direct
}
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", net.JoinHostPort(metadata.String(), metadata.Port), tcpTimeout)
if err != nil { if err != nil {
return return nil, err
} }
tcpKeepAlive(c) tcpKeepAlive(c)
return &DirectAdapter{conn: c}, nil return c, nil
}
func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, nil, err
}
return pc, addr, nil
} }
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{} return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
udp: true,
},
}
} }

View File

@ -1,21 +1,18 @@
package adapters package adapters
import ( import (
"encoding/json"
"errors" "errors"
"net"
"sync" "sync"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type proxy struct {
RawProxy C.Proxy
Valid bool
}
type Fallback struct { type Fallback struct {
name string *Base
proxies []*proxy proxies []C.Proxy
rawURL string rawURL string
interval time.Duration interval time.Duration
done chan struct{} done chan struct{}
@ -28,42 +25,39 @@ type FallbackOption struct {
Interval int `proxy:"interval"` Interval int `proxy:"interval"`
} }
func (f *Fallback) Name() string {
return f.name
}
func (f *Fallback) Type() C.AdapterType {
return C.Fallback
}
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
_, proxy := f.findNextValidProxy(0) proxy := f.findAliveProxy()
if proxy != nil { return proxy.Name()
return proxy.RawProxy.Name()
}
return f.proxies[0].RawProxy.Name()
} }
func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) {
idx := 0 proxy := f.findAliveProxy()
var proxy *proxy return proxy.Dial(metadata)
for {
idx, proxy = f.findNextValidProxy(idx)
if proxy == nil {
break
}
adapter, err = proxy.RawProxy.Generator(metadata)
if err != nil {
proxy.Valid = false
idx++
continue
}
return
}
return f.proxies[0].RawProxy.Generator(metadata)
} }
func (f *Fallback) Close() { func (f *Fallback) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
proxy := f.findAliveProxy()
return proxy.DialUDP(metadata)
}
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
return proxy.SupportUDP()
}
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
func (f *Fallback) Destroy() {
f.done <- struct{}{} f.done <- struct{}{}
} }
@ -81,13 +75,13 @@ Loop:
} }
} }
func (f *Fallback) findNextValidProxy(start int) (int, *proxy) { func (f *Fallback) findAliveProxy() C.Proxy {
for i := start; i < len(f.proxies); i++ { for _, proxy := range f.proxies {
if f.proxies[i].Valid { if proxy.Alive() {
return i, f.proxies[i] return proxy
} }
} }
return -1, nil return f.proxies[0]
} }
func (f *Fallback) validTest() { func (f *Fallback) validTest() {
@ -95,9 +89,8 @@ func (f *Fallback) validTest() {
wg.Add(len(f.proxies)) wg.Add(len(f.proxies))
for _, p := range f.proxies { for _, p := range f.proxies {
go func(p *proxy) { go func(p C.Proxy) {
_, err := DelayTest(p.RawProxy, f.rawURL) p.URLTest(f.rawURL)
p.Valid = err == nil
wg.Done() wg.Done()
}(p) }(p)
} }
@ -116,17 +109,13 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
} }
interval := time.Duration(option.Interval) * time.Second interval := time.Duration(option.Interval) * time.Second
warpperProxies := make([]*proxy, len(proxies))
for idx := range proxies {
warpperProxies[idx] = &proxy{
RawProxy: proxies[idx],
Valid: true,
}
}
Fallback := &Fallback{ Fallback := &Fallback{
name: option.Name, Base: &Base{
proxies: warpperProxies, name: option.Name,
tp: C.Fallback,
},
proxies: proxies,
rawURL: option.URL, rawURL: option.URL,
interval: interval, interval: interval,
done: make(chan struct{}), done: make(chan struct{}),

120
adapters/outbound/http.go Normal file
View File

@ -0,0 +1,120 @@
package adapters
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
C "github.com/Dreamacro/clash/constant"
)
type Http struct {
*Base
addr string
user string
pass string
tls bool
skipCertVerify bool
tlsConfig *tls.Config
}
type HttpOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", h.addr, tcpTimeout)
if err == nil && h.tls {
cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake()
c = cc
}
if err != nil {
return nil, fmt.Errorf("%s connect error", h.addr)
}
tcpKeepAlive(c)
if err := h.shakeHand(metadata, c); err != nil {
return nil, err
}
return c, nil
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
var buf bytes.Buffer
var err error
addr := net.JoinHostPort(metadata.String(), metadata.DstPort)
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
buf.WriteString("Host: " + metadata.String() + "\r\n")
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
buf.WriteString("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n")
}
// header ended
buf.WriteString("\r\n")
_, err = rw.Write(buf.Bytes())
if err != nil {
return err
}
var req http.Request
resp, err := http.ReadResponse(bufio.NewReader(rw), &req)
if err != nil {
return err
}
if resp.StatusCode == 200 {
return nil
}
if resp.StatusCode == 407 {
return errors.New("HTTP need auth")
}
if resp.StatusCode == 405 {
return errors.New("CONNECT method not allowed by proxy")
}
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
}
func NewHttp(option HttpOption) *Http {
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server,
}
}
return &Http{
Base: &Base{
name: option.Name,
tp: C.Http,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,
pass: option.Password,
tls: option.TLS,
skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig,
}
}

View File

@ -0,0 +1,158 @@
package adapters
import (
"encoding/json"
"errors"
"net"
"sync"
"time"
"github.com/Dreamacro/clash/common/murmur3"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/publicsuffix"
)
type LoadBalance struct {
*Base
proxies []C.Proxy
maxRetry int
rawURL string
interval time.Duration
done chan struct{}
}
func getKey(metadata *C.Metadata) string {
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if metadata.DstIP == nil {
return ""
}
return metadata.DstIP.String()
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := lb.proxies[idx]
if proxy.Alive() {
return proxy.Dial(metadata)
}
}
return lb.proxies[0].Dial(metadata)
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := lb.proxies[idx]
if proxy.Alive() {
return proxy.DialUDP(metadata)
}
}
return lb.proxies[0].DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
func (lb *LoadBalance) Destroy() {
lb.done <- struct{}{}
}
func (lb *LoadBalance) validTest() {
wg := sync.WaitGroup{}
wg.Add(len(lb.proxies))
for _, p := range lb.proxies {
go func(p C.Proxy) {
p.URLTest(lb.rawURL)
wg.Done()
}(p)
}
wg.Wait()
}
func (lb *LoadBalance) loop() {
tick := time.NewTicker(lb.interval)
go lb.validTest()
Loop:
for {
select {
case <-tick.C:
go lb.validTest()
case <-lb.done:
break Loop
}
}
}
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.proxies {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": lb.Type().String(),
"all": all,
})
}
type LoadBalanceOption struct {
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
}
func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) {
if len(proxies) == 0 {
return nil, errors.New("Provide at least one proxy")
}
interval := time.Duration(option.Interval) * time.Second
lb := &LoadBalance{
Base: &Base{
name: option.Name,
tp: C.LoadBalance,
},
proxies: proxies,
maxRetry: 3,
rawURL: option.URL,
interval: interval,
done: make(chan struct{}),
}
go lb.loop()
return lb, nil
}

View File

@ -8,42 +8,27 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// RejectAdapter is a reject connected adapter
type RejectAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (r *RejectAdapter) Close() {}
// Conn is used to http request
func (r *RejectAdapter) Conn() net.Conn {
return r.conn
}
type Reject struct { type Reject struct {
*Base
} }
func (r *Reject) Name() string { func (r *Reject) Dial(metadata *C.Metadata) (net.Conn, error) {
return "REJECT" return &NopConn{}, nil
}
func (r *Reject) Type() C.AdapterType {
return C.Reject
}
func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
return &RejectAdapter{conn: &NopConn{}}, nil
} }
func NewReject() *Reject { func NewReject() *Reject {
return &Reject{} return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
},
}
} }
type NopConn struct{} type NopConn struct{}
func (rw *NopConn) Read(b []byte) (int, error) { func (rw *NopConn) Read(b []byte) (int, error) {
return len(b), nil return 0, io.EOF
} }
func (rw *NopConn) Write(b []byte) (int, error) { func (rw *NopConn) Write(b []byte) (int, error) {

View File

@ -1,14 +1,16 @@
package adapters package adapters
import ( import (
"encoding/json"
"errors" "errors"
"net"
"sort" "sort"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Selector struct { type Selector struct {
name string *Base
selected C.Proxy selected C.Proxy
proxies map[string]C.Proxy proxies map[string]C.Proxy
} }
@ -18,29 +20,33 @@ type SelectorOption struct {
Proxies []string `proxy:"proxies"` Proxies []string `proxy:"proxies"`
} }
func (s *Selector) Name() string { func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) {
return s.name return s.selected.Dial(metadata)
} }
func (s *Selector) Type() C.AdapterType { func (s *Selector) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return C.Selector return s.selected.DialUDP(metadata)
} }
func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (s *Selector) SupportUDP() bool {
return s.selected.Generator(metadata) return s.selected.SupportUDP()
} }
func (s *Selector) Now() string { func (s *Selector) MarshalJSON() ([]byte, error) {
return s.selected.Name()
}
func (s *Selector) All() []string {
var all []string var all []string
for k := range s.proxies { for k := range s.proxies {
all = append(all, k) all = append(all, k)
} }
sort.Strings(all) sort.Strings(all)
return all return json.Marshal(map[string]interface{}{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
})
}
func (s *Selector) Now() string {
return s.selected.Name()
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
@ -63,7 +69,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
} }
s := &Selector{ s := &Selector{
name: name, Base: &Base{
name: name,
tp: C.Selector,
},
proxies: mapping, proxies: mapping,
selected: proxies[0], selected: proxies[0],
} }

View File

@ -1,74 +1,110 @@
package adapters package adapters
import ( import (
"bytes" "crypto/tls"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/simple-obfs" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/socks5"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/socks"
) )
// ShadowsocksAdapter is a shadowsocks adapter
type ShadowsocksAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *ShadowsocksAdapter) Close() {
ss.conn.Close()
}
func (ss *ShadowsocksAdapter) Conn() net.Conn {
return ss.conn
}
type ShadowSocks struct { type ShadowSocks struct {
server string *Base
name string server string
obfs string cipher core.Cipher
obfsHost string
cipher core.Cipher // obfs
obfsMode string
obfsOption *simpleObfsOption
wsOption *v2rayObfs.WebsocketOption
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"` Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"` ObfsHost string `proxy:"obfs-host,omitempty"`
} }
func (ss *ShadowSocks) Name() string { type simpleObfsOption struct {
return ss.name Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
} }
func (ss *ShadowSocks) Type() C.AdapterType { type v2rayObfsOption struct {
return C.Shadowsocks Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
} }
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", ss.server) return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
} }
tcpKeepAlive(c) tcpKeepAlive(c)
switch ss.obfs { switch ss.obfsMode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost) c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http": case "http":
_, port, _ := net.SplitHostPort(ss.server) _, port, _ := net.SplitHostPort(ss.server)
c = obfs.NewHTTPObfs(c, ss.obfsHost, port) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewWebsocketObfs(c, ss.wsOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
}
} }
c = ss.cipher.StreamConn(c) c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata)) _, err = c.Write(serializesSocksAddr(metadata))
return &ShadowsocksAdapter{conn: c}, err return c, err
}
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, err := net.ResolveUDPAddr("udp", ss.server)
if err != nil {
return nil, nil, err
}
remoteAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, nil, err
}
pc = ss.cipher.PacketConn(pc)
return &ssUDPConn{PacketConn: pc, rAddr: remoteAddr}, addr, nil
}
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
} }
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
@ -80,37 +116,83 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
} }
obfs := option.Obfs var wsOption *v2rayObfs.WebsocketOption
obfsHost := "bing.com" var obfsOption *simpleObfsOption
if option.ObfsHost != "" { obfsMode := ""
obfsHost = option.ObfsHost
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
}
obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
wsOption = &v2rayObfs.WebsocketOption{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
TLSConfig: tlsConfig,
}
} }
return &ShadowSocks{ return &ShadowSocks{
server: server, Base: &Base{
name: option.Name, name: option.Name,
cipher: ciph, tp: C.Shadowsocks,
obfs: obfs, udp: option.UDP,
obfsHost: obfsHost, },
server: server,
cipher: ciph,
obfsMode: obfsMode,
wsOption: wsOption,
obfsOption: obfsOption,
}, nil }, nil
} }
func serializesSocksAddr(metadata *C.Metadata) []byte { type ssUDPConn struct {
var buf [][]byte net.PacketConn
aType := uint8(metadata.AddrType) rAddr net.Addr
p, _ := strconv.Atoi(metadata.Port) }
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType { func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
case socks.AtypDomainName: buf := pool.BufPool.Get().([]byte)
len := uint8(len(metadata.Host)) defer pool.BufPool.Put(buf[:cap(buf)])
host := []byte(metadata.Host) rAddr := socks5.ParseAddr(uc.rAddr.String())
buf = [][]byte{{aType, len}, host, port} copy(buf[len(rAddr):], b)
case socks.AtypIPv4: copy(buf, rAddr)
host := metadata.IP.To4() return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr)
buf = [][]byte{{aType}, host, port} }
case socks.AtypIPv6:
host := metadata.IP.To16() func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
buf = [][]byte{{aType}, host, port} n, a, e := uc.PacketConn.ReadFrom(b)
} addr := socks5.SplitAddr(b[:n])
return bytes.Join(buf, nil) copy(b, b[len(addr):])
return n - len(addr), a, e
} }

View File

@ -1,36 +1,20 @@
package adapters package adapters
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/socks"
) )
// Socks5Adapter is a shadowsocks adapter
type Socks5Adapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *Socks5Adapter) Close() {
ss.conn.Close()
}
func (ss *Socks5Adapter) Conn() net.Conn {
return ss.conn
}
type Socks5 struct { type Socks5 struct {
*Base
addr string addr string
name string user string
pass string
tls bool tls bool
skipCertVerify bool skipCertVerify bool
tlsConfig *tls.Config tlsConfig *tls.Config
@ -40,19 +24,14 @@ type Socks5Option struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Socks5) Name() string { func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
return ss.name
}
func (ss *Socks5) Type() C.AdapterType {
return C.Socks5
}
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls { if err == nil && ss.tls {
@ -65,41 +44,44 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
return nil, fmt.Errorf("%s connect error", ss.addr) return nil, fmt.Errorf("%s connect error", ss.addr)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
if err := ss.shakeHand(metadata, c); err != nil { var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err return nil, err
} }
return &Socks5Adapter{conn: c}, nil return c, nil
} }
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
buf := make([]byte, socks.MaxAddrLen) c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake()
c = cc
}
// VER, CMD, RSV
_, err := rw.Write([]byte{5, 1, 0})
if err != nil { if err != nil {
return err return nil, nil, fmt.Errorf("%s connect error", ss.addr)
}
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
} }
if _, err := io.ReadFull(rw, buf[:2]); err != nil { if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user); err != nil {
return err return nil, nil, err
} }
return &fakeUDPConn{Conn: c}, c.LocalAddr(), nil
if buf[0] != 5 {
return errors.New("SOCKS version error")
} else if buf[1] != 0 {
return errors.New("SOCKS need auth")
}
// VER, CMD, RSV, ADDR
if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(metadata)}, []byte(""))); err != nil {
return err
}
if _, err := io.ReadFull(rw, buf[:10]); err != nil {
return err
}
return nil
} }
func NewSocks5(option Socks5Option) *Socks5 { func NewSocks5(option Socks5Option) *Socks5 {
@ -108,15 +90,19 @@ func NewSocks5(option Socks5Option) *Socks5 {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(), ClientSessionCache: getClientSessionCache(),
MinVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS12,
ServerName: option.Server, ServerName: option.Server,
} }
} }
return &Socks5{ return &Socks5{
Base: &Base{
name: option.Name,
tp: C.Socks5,
udp: option.UDP,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name, user: option.UserName,
pass: option.Password,
tls: option.TLS, tls: option.TLS,
skipCertVerify: option.SkipCertVerify, skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,

View File

@ -1,16 +1,21 @@
package adapters package adapters
import ( import (
"context"
"encoding/json"
"errors" "errors"
"net"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/Dreamacro/clash/common/picker"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type URLTest struct { type URLTest struct {
name string *Base
proxies []C.Proxy proxies []C.Proxy
rawURL string rawURL string
fast C.Proxy fast C.Proxy
@ -26,27 +31,40 @@ type URLTestOption struct {
Interval int `proxy:"interval"` Interval int `proxy:"interval"`
} }
func (u *URLTest) Name() string {
return u.name
}
func (u *URLTest) Type() C.AdapterType {
return C.URLTest
}
func (u *URLTest) Now() string { func (u *URLTest) Now() string {
return u.fast.Name() return u.fast.Name()
} }
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
a, err := u.fast.Generator(metadata) a, err := u.fast.Dial(metadata)
if err != nil { if err != nil {
go u.speedTest() u.fallback()
} }
return a, err return a, err
} }
func (u *URLTest) Close() { func (u *URLTest) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
return u.fast.DialUDP(metadata)
}
func (u *URLTest) SupportUDP() bool {
return u.fast.SupportUDP()
}
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies {
all = append(all, proxy.Name())
}
sort.Strings(all)
return json.Marshal(map[string]interface{}{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func (u *URLTest) Destroy() {
u.done <- struct{}{} u.done <- struct{}{}
} }
@ -64,8 +82,25 @@ Loop:
} }
} }
func (u *URLTest) fallback() {
fast := u.proxies[0]
min := fast.LastDelay()
for _, proxy := range u.proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
u.fast = fast
}
func (u *URLTest) speedTest() { func (u *URLTest) speedTest() {
if atomic.AddInt32(&u.once, 1) != 1 { if !atomic.CompareAndSwapInt32(&u.once, 0, 1) {
return return
} }
defer atomic.StoreInt32(&u.once, 0) defer atomic.StoreInt32(&u.once, 0)
@ -73,12 +108,12 @@ func (u *URLTest) speedTest() {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(u.proxies)) wg.Add(len(u.proxies))
c := make(chan interface{}) c := make(chan interface{})
fast := selectFast(c) fast := picker.SelectFast(context.Background(), c)
timer := time.NewTimer(u.interval) timer := time.NewTimer(u.interval)
for _, p := range u.proxies { for _, p := range u.proxies {
go func(p C.Proxy) { go func(p C.Proxy) {
_, err := DelayTest(p, u.rawURL) _, err := p.URLTest(u.rawURL)
if err == nil { if err == nil {
c <- p c <- p
} }
@ -113,7 +148,10 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
interval := time.Duration(option.Interval) * time.Second interval := time.Duration(option.Interval) * time.Second
urlTest := &URLTest{ urlTest := &URLTest{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.URLTest,
},
proxies: proxies[:], proxies: proxies[:],
rawURL: option.URL, rawURL: option.URL,
fast: proxies[0], fast: proxies[0],

View File

@ -1,14 +1,16 @@
package adapters package adapters
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http"
"net/url" "net/url"
"strconv"
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -21,39 +23,6 @@ var (
once sync.Once once sync.Once
) )
// DelayTest get the delay for the specified URL
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := proxy.Generator(&addr)
if err != nil {
return
}
defer instance.Close()
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance.Conn(), nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Get(url)
if err != nil {
return
}
resp.Body.Close()
t = int16(time.Since(start) / time.Millisecond)
return
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) { func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL) u, err := url.Parse(rawURL)
if err != nil { if err != nil {
@ -75,27 +44,12 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
addr = C.Metadata{ addr = C.Metadata{
AddrType: C.AtypDomainName, AddrType: C.AtypDomainName,
Host: u.Hostname(), Host: u.Hostname(),
IP: nil, DstIP: nil,
Port: port, DstPort: port,
} }
return return
} }
func selectFast(in chan interface{}) chan interface{} {
out := make(chan interface{})
go func() {
p, open := <-in
if open {
out <- p
}
close(out)
for range in {
}
}()
return out
}
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) tcp.SetKeepAlive(true)
@ -109,3 +63,36 @@ func getClientSessionCache() tls.ClientSessionCache {
}) })
return globalClientSessionCache return globalClientSessionCache
} }
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
aType := uint8(metadata.AddrType)
p, _ := strconv.Atoi(metadata.DstPort)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
case socks5.AtypDomainName:
len := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, len}, host, port}
case socks5.AtypIPv4:
host := metadata.DstIP.To4()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
host := metadata.DstIP.To16()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
type fakeUDPConn struct {
net.Conn
}
func (fuc *fakeUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return fuc.Conn.Write(b)
}
func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := fuc.Conn.Read(b)
return n, fuc.RemoteAddr(), err
}

View File

@ -10,76 +10,72 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// VmessAdapter is a vmess adapter
type VmessAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (v *VmessAdapter) Close() {
v.conn.Close()
}
func (v *VmessAdapter) Conn() net.Conn {
return v.conn
}
type Vmess struct { type Vmess struct {
name string *Base
server string server string
client *vmess.Client client *vmess.Client
} }
type VmessOption struct { type VmessOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"` AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
Network string `proxy:"network,omitempty"` UDP bool `proxy:"udp,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` Network string `proxy:"network,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Vmess) Name() string { func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) {
return ss.name c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
}
func (ss *Vmess) Type() C.AdapterType {
return C.Vmess
}
func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", ss.server) return nil, fmt.Errorf("%s connect error", v.server)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = ss.client.New(c, parseVmessAddr(metadata)) c, err = v.client.New(c, parseVmessAddr(metadata))
return &VmessAdapter{conn: c}, err return c, err
}
func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
if err != nil {
return nil, nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
return &fakeUDPConn{Conn: c}, c.LocalAddr(), err
} }
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID, UUID: option.UUID,
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
TLS: option.TLS, TLS: option.TLS,
Host: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), HostName: option.Server,
NetWork: option.Network, Port: strconv.Itoa(option.Port),
WebSocketPath: option.WSPath, NetWork: option.Network,
SkipCertVerify: option.SkipCertVerify, WebSocketPath: option.WSPath,
SessionCacahe: getClientSessionCache(), WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify,
SessionCacahe: getClientSessionCache(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Vmess{ return &Vmess{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.Vmess,
udp: option.UDP,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client, client: client,
}, nil }, nil
@ -92,11 +88,11 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
case C.AtypIPv4: case C.AtypIPv4:
addrType = byte(vmess.AtypIPv4) addrType = byte(vmess.AtypIPv4)
addr = make([]byte, net.IPv4len) addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.IP.To4()) copy(addr[:], metadata.DstIP.To4())
case C.AtypIPv6: case C.AtypIPv6:
addrType = byte(vmess.AtypIPv6) addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len) addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.IP.To16()) copy(addr[:], metadata.DstIP.To16())
case C.AtypDomainName: case C.AtypDomainName:
addrType = byte(vmess.AtypDomainName) addrType = byte(vmess.AtypDomainName)
addr = make([]byte, len(metadata.Host)+1) addr = make([]byte, len(metadata.Host)+1)
@ -104,8 +100,9 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
copy(addr[1:], []byte(metadata.Host)) copy(addr[1:], []byte(metadata.Host))
} }
port, _ := strconv.Atoi(metadata.Port) port, _ := strconv.Atoi(metadata.DstPort)
return &vmess.DstAddr{ return &vmess.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType, AddrType: addrType,
Addr: addr, Addr: addr,
Port: uint(port), Port: uint(port),

106
common/cache/cache.go vendored Normal file
View File

@ -0,0 +1,106 @@
package cache
import (
"runtime"
"sync"
"time"
)
// Cache store element with a expired time
type Cache struct {
*cache
}
type cache struct {
mapping sync.Map
janitor *janitor
}
type element struct {
Expired time.Time
Payload interface{}
}
// Put element in Cache with its ttl
func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) {
c.mapping.Store(key, &element{
Payload: payload,
Expired: time.Now().Add(ttl),
})
}
// Get element in Cache, and drop when it expired
func (c *cache) Get(key interface{}) interface{} {
item, exist := c.mapping.Load(key)
if !exist {
return nil
}
elm := item.(*element)
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return nil
}
return elm.Payload
}
// GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) {
item, exist := c.mapping.Load(key)
if !exist {
return
}
elm := item.(*element)
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return
}
return elm.Payload, elm.Expired
}
func (c *cache) cleanup() {
c.mapping.Range(func(k, v interface{}) bool {
key := k.(string)
elm := v.(*element)
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
}
return true
})
}
type janitor struct {
interval time.Duration
stop chan struct{}
}
func (j *janitor) process(c *cache) {
ticker := time.NewTicker(j.interval)
for {
select {
case <-ticker.C:
c.cleanup()
case <-j.stop:
ticker.Stop()
return
}
}
}
func stopJanitor(c *Cache) {
c.janitor.stop <- struct{}{}
}
// New return *Cache
func New(interval time.Duration) *Cache {
j := &janitor{
interval: interval,
stop: make(chan struct{}),
}
c := &cache{janitor: j}
go j.process(c)
C := &Cache{c}
runtime.SetFinalizer(C, stopJanitor)
return C
}

70
common/cache/cache_test.go vendored Normal file
View File

@ -0,0 +1,70 @@
package cache
import (
"runtime"
"testing"
"time"
)
func TestCache_Basic(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
c.Put("string", "a", ttl)
i := c.Get("int")
if i.(int) != 1 {
t.Error("should recv 1")
}
s := c.Get("string")
if s.(string) != "a" {
t.Error("should recv 'a'")
}
}
func TestCache_TTL(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
i := c.Get("int")
if i.(int) != 1 {
t.Error("should recv 1")
}
time.Sleep(ttl * 2)
i = c.Get("int")
if i != nil {
t.Error("should recv nil")
}
}
func TestCache_AutoCleanup(t *testing.T) {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
time.Sleep(ttl * 2)
i := c.Get("int")
if i != nil {
t.Error("should recv nil")
}
}
func TestCache_AutoGC(t *testing.T) {
sign := make(chan struct{})
go func() {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
sign <- struct{}{}
}()
<-sign
runtime.GC()
}

50
common/murmur3/murmur.go Normal file
View File

@ -0,0 +1,50 @@
package murmur3
type bmixer interface {
bmix(p []byte) (tail []byte)
Size() (n int)
reset()
}
type digest struct {
clen int // Digested input cumulative length.
tail []byte // 0 to Size()-1 bytes view of `buf'.
buf [16]byte // Expected (but not required) to be Size() large.
seed uint32 // Seed for initializing the hash.
bmixer
}
func (d *digest) BlockSize() int { return 1 }
func (d *digest) Write(p []byte) (n int, err error) {
n = len(p)
d.clen += n
if len(d.tail) > 0 {
// Stick back pending bytes.
nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1].
if nfree < len(p) {
// One full block can be formed.
block := append(d.tail, p[:nfree]...)
p = p[nfree:]
_ = d.bmix(block) // No tail.
} else {
// Tail's buf is large enough to prevent reallocs.
p = append(d.tail, p...)
}
}
d.tail = d.bmix(p)
// Keep own copy of the 0 to Size()-1 pending bytes.
nn := copy(d.buf[:], d.tail)
d.tail = d.buf[:nn]
return n, nil
}
func (d *digest) Reset() {
d.clen = 0
d.tail = nil
d.bmixer.reset()
}

149
common/murmur3/murmur32.go Normal file
View File

@ -0,0 +1,149 @@
package murmur3
// https://github.com/spaolacci/murmur3/blob/master/murmur32.go
import (
"hash"
"unsafe"
)
// Make sure interfaces are correctly implemented.
var (
_ hash.Hash32 = new(digest32)
_ bmixer = new(digest32)
)
const (
c1_32 uint32 = 0xcc9e2d51
c2_32 uint32 = 0x1b873593
)
// digest32 represents a partial evaluation of a 32 bites hash.
type digest32 struct {
digest
h1 uint32 // Unfinalized running hash.
}
// New32 returns new 32-bit hasher
func New32() hash.Hash32 { return New32WithSeed(0) }
// New32WithSeed returns new 32-bit hasher set with explicit seed value
func New32WithSeed(seed uint32) hash.Hash32 {
d := new(digest32)
d.seed = seed
d.bmixer = d
d.Reset()
return d
}
func (d *digest32) Size() int { return 4 }
func (d *digest32) reset() { d.h1 = d.seed }
func (d *digest32) Sum(b []byte) []byte {
h := d.Sum32()
return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h))
}
// Digest as many blocks as possible.
func (d *digest32) bmix(p []byte) (tail []byte) {
h1 := d.h1
nblocks := len(p) / 4
for i := 0; i < nblocks; i++ {
k1 := *(*uint32)(unsafe.Pointer(&p[i*4]))
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
h1 = h1*4 + h1 + 0xe6546b64
}
d.h1 = h1
return p[nblocks*d.Size():]
}
func (d *digest32) Sum32() (h1 uint32) {
h1 = d.h1
var k1 uint32
switch len(d.tail) & 3 {
case 3:
k1 ^= uint32(d.tail[2]) << 16
fallthrough
case 2:
k1 ^= uint32(d.tail[1]) << 8
fallthrough
case 1:
k1 ^= uint32(d.tail[0])
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
}
h1 ^= uint32(d.clen)
h1 ^= h1 >> 16
h1 *= 0x85ebca6b
h1 ^= h1 >> 13
h1 *= 0xc2b2ae35
h1 ^= h1 >> 16
return h1
}
func Sum32(data []byte) uint32 { return Sum32WithSeed(data, 0) }
func Sum32WithSeed(data []byte, seed uint32) uint32 {
h1 := seed
nblocks := len(data) / 4
var p uintptr
if len(data) > 0 {
p = uintptr(unsafe.Pointer(&data[0]))
}
p1 := p + uintptr(4*nblocks)
for ; p < p1; p += 4 {
k1 := *(*uint32)(unsafe.Pointer(p))
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
h1 = h1*4 + h1 + 0xe6546b64
}
tail := data[nblocks*4:]
var k1 uint32
switch len(tail) & 3 {
case 3:
k1 ^= uint32(tail[2]) << 16
fallthrough
case 2:
k1 ^= uint32(tail[1]) << 8
fallthrough
case 1:
k1 ^= uint32(tail[0])
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
}
h1 ^= uint32(len(data))
h1 ^= h1 >> 16
h1 *= 0x85ebca6b
h1 ^= h1 >> 13
h1 *= 0xc2b2ae35
h1 ^= h1 >> 16
return h1
}

22
common/picker/picker.go Normal file
View File

@ -0,0 +1,22 @@
package picker
import "context"
func SelectFast(ctx context.Context, in <-chan interface{}) <-chan interface{} {
out := make(chan interface{})
go func() {
select {
case p, open := <-in:
if open {
out <- p
}
case <-ctx.Done():
}
close(out)
for range in {
}
}()
return out
}

View File

@ -0,0 +1,44 @@
package picker
import (
"context"
"testing"
"time"
)
func sleepAndSend(delay int, in chan<- interface{}, input interface{}) {
time.Sleep(time.Millisecond * time.Duration(delay))
in <- input
}
func sleepAndClose(delay int, in chan interface{}) {
time.Sleep(time.Millisecond * time.Duration(delay))
close(in)
}
func TestPicker_Basic(t *testing.T) {
in := make(chan interface{})
fast := SelectFast(context.Background(), in)
go sleepAndSend(20, in, 1)
go sleepAndSend(30, in, 2)
go sleepAndClose(40, in)
number, exist := <-fast
if !exist || number != 1 {
t.Error("should recv 1", exist, number)
}
}
func TestPicker_Timeout(t *testing.T) {
in := make(chan interface{})
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*5)
defer cancel()
fast := SelectFast(ctx, in)
go sleepAndSend(20, in, 1)
go sleepAndClose(30, in)
_, exist := <-fast
if exist {
t.Error("should recv false")
}
}

15
common/pool/pool.go Normal file
View File

@ -0,0 +1,15 @@
package pool
import (
"sync"
)
const (
// io.Copy default buffer size is 32 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
bufferSize = 20 * 1024
)
// BufPool provide buffer for relay
var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}

71
common/queue/queue.go Normal file
View File

@ -0,0 +1,71 @@
package queue
import (
"sync"
)
// Queue is a simple concurrent safe queue
type Queue struct {
items []interface{}
lock sync.RWMutex
}
// Put add the item to the queue.
func (q *Queue) Put(items ...interface{}) {
if len(items) == 0 {
return
}
q.lock.Lock()
q.items = append(q.items, items...)
q.lock.Unlock()
}
// Pop returns the head of items.
func (q *Queue) Pop() interface{} {
if len(q.items) == 0 {
return nil
}
q.lock.Lock()
head := q.items[0]
q.items = q.items[1:]
q.lock.Unlock()
return head
}
// First returns the head of items without deleting.
func (q *Queue) First() interface{} {
if len(q.items) == 0 {
return nil
}
q.lock.RLock()
head := q.items[0]
q.lock.RUnlock()
return head
}
// Copy get the copy of queue.
func (q *Queue) Copy() []interface{} {
items := []interface{}{}
q.lock.RLock()
items = append(items, q.items...)
q.lock.RUnlock()
return items
}
// Len returns the number of items in this queue.
func (q *Queue) Len() int64 {
q.lock.Lock()
defer q.lock.Unlock()
return int64(len(q.items))
}
// New is a constructor for a new concurrent safe queue.
func New(hint int64) *Queue {
return &Queue{
items: make([]interface{}, 0, hint),
}
}

View File

@ -1,5 +1,7 @@
package structure package structure
// references: https://github.com/mitchellh/mapstructure
import ( import (
"fmt" "fmt"
"reflect" "reflect"
@ -45,11 +47,11 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
} }
value, ok := src[key] value, ok := src[key]
if !ok { if !ok || value == nil {
if omitempty { if omitempty {
continue continue
} }
return fmt.Errorf("key %s missing", key) return fmt.Errorf("key '%s' missing", key)
} }
err := d.decode(key, value, v.Field(idx)) err := d.decode(key, value, v.Field(idx))
@ -70,6 +72,10 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
return d.decodeBool(name, data, val) return d.decodeBool(name, data, val)
case reflect.Slice: case reflect.Slice:
return d.decodeSlice(name, data, val) return d.decodeSlice(name, data, val)
case reflect.Map:
return d.decodeMap(name, data, val)
case reflect.Interface:
return d.setInterface(name, data, val)
default: default:
return fmt.Errorf("type %s not support", val.Kind().String()) return fmt.Errorf("type %s not support", val.Kind().String())
} }
@ -108,7 +114,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
val.SetString(strconv.FormatInt(dataVal.Int(), 10)) val.SetString(strconv.FormatInt(dataVal.Int(), 10))
default: default:
err = fmt.Errorf( err = fmt.Errorf(
"'%s' expected type'%s', got unconvertible type '%s'", "'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type(), name, val.Type(), dataVal.Type(),
) )
} }
@ -125,7 +131,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (
val.SetBool(dataVal.Int() != 0) val.SetBool(dataVal.Int() != 0)
default: default:
err = fmt.Errorf( err = fmt.Errorf(
"'%s' expected type'%s', got unconvertible type '%s'", "'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type(), name, val.Type(), dataVal.Type(),
) )
} }
@ -158,3 +164,76 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
val.Set(valSlice) val.Set(valSlice)
return nil return nil
} }
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
valMap := val
if valMap.IsNil() {
mapType := reflect.MapOf(valKeyType, valElemType)
valMap = reflect.MakeMap(mapType)
}
dataVal := reflect.Indirect(reflect.ValueOf(data))
if dataVal.Kind() != reflect.Map {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
}
return d.decodeMapFromMap(name, dataVal, val, valMap)
}
func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
errors := make([]string, 0)
if dataVal.Len() == 0 {
if dataVal.IsNil() {
if !val.IsNil() {
val.Set(dataVal)
}
} else {
val.Set(valMap)
}
return nil
}
for _, k := range dataVal.MapKeys() {
fieldName := fmt.Sprintf("%s[%s]", name, k)
currentKey := reflect.Indirect(reflect.New(valKeyType))
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
errors = append(errors, err.Error())
continue
}
v := dataVal.MapIndex(k).Interface()
currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil {
errors = append(errors, err.Error())
continue
}
valMap.SetMapIndex(currentKey, currentVal)
}
val.Set(valMap)
if len(errors) > 0 {
return fmt.Errorf(strings.Join(errors, ","))
}
return nil
}
func (d *Decoder) setInterface(name string, data interface{}, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data)
val.Set(dataVal)
return nil
}

50
component/fakeip/pool.go Normal file
View File

@ -0,0 +1,50 @@
package fakeip
import (
"errors"
"net"
)
// Pool is a implementation about fake ip generator without storage
type Pool struct {
max uint32
min uint32
offset uint32
}
// Get return a new fake ip
func (p *Pool) Get() net.IP {
ip := uintToIP(p.min + p.offset)
p.offset = (p.offset + 1) % (p.max - p.min)
return ip
}
func ipToUint(ip net.IP) uint32 {
v := uint32(ip[0]) << 24
v += uint32(ip[1]) << 16
v += uint32(ip[2]) << 8
v += uint32(ip[3])
return v
}
func uintToIP(v uint32) net.IP {
return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
}
// New return Pool instance
func New(ipnet *net.IPNet) (*Pool, error) {
min := ipToUint(ipnet.IP) + 1
ones, bits := ipnet.Mask.Size()
total := 1<<uint(bits-ones) - 2
if total <= 0 {
return nil, errors.New("ipnet don't have valid ip")
}
max := min + uint32(total)
return &Pool{
min: min,
max: max,
}, nil
}

View File

@ -0,0 +1,44 @@
package fakeip
import (
"net"
"testing"
)
func TestPool_Basic(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
pool, _ := New(ipnet)
first := pool.Get()
last := pool.Get()
if !first.Equal(net.IP{192, 168, 0, 1}) {
t.Error("should get right first ip, instead of", first.String())
}
if !last.Equal(net.IP{192, 168, 0, 2}) {
t.Error("should get right last ip, instead of", first.String())
}
}
func TestPool_Cycle(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
pool, _ := New(ipnet)
first := pool.Get()
pool.Get()
same := pool.Get()
if !first.Equal(same) {
t.Error("should return same ip", first.String())
}
}
func TestPool_Error(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/31")
_, err := New(ipnet)
if err == nil {
t.Error("should return err")
}
}

View File

@ -8,6 +8,8 @@ import (
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"github.com/Dreamacro/clash/common/pool"
) )
// HTTPObfs is shadowsocks http simple-obfs implementation // HTTPObfs is shadowsocks http simple-obfs implementation
@ -32,15 +34,15 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
} }
if ho.firstResponse { if ho.firstResponse {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
n, err := ho.Conn.Read(buf) n, err := ho.Conn.Read(buf)
if err != nil { if err != nil {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(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 {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, io.EOF return 0, io.EOF
} }
ho.firstResponse = false ho.firstResponse = false
@ -50,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 {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
} }
return n, nil return n, nil
} }

View File

@ -6,15 +6,18 @@ import (
"io" "io"
"math/rand" "math/rand"
"net" "net"
"sync"
"time" "time"
"github.com/Dreamacro/clash/common/pool"
) )
func init() { func init() {
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
} }
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, 2048) }} const (
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
)
// TLSObfs is shadowsocks tls simple-obfs implementation // TLSObfs is shadowsocks tls simple-obfs implementation
type TLSObfs struct { type TLSObfs struct {
@ -26,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 := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err := io.ReadFull(to.Conn, buf[:discardN]) _, err := io.ReadFull(to.Conn, buf[:discardN])
if err != nil { if err != nil {
return 0, err return 0, err
} }
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
sizeBuf := make([]byte, 2) sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf) _, err = io.ReadFull(to.Conn, sizeBuf)
@ -75,8 +78,23 @@ func (to *TLSObfs) Read(b []byte) (int, error) {
// type + ver = 3 // type + ver = 3
return to.read(b, 3) return to.read(b, 3)
} }
func (to *TLSObfs) Write(b []byte) (int, error) { func (to *TLSObfs) Write(b []byte) (int, error) {
length := len(b)
for i := 0; i < length; i += chunkSize {
end := i + chunkSize
if end > length {
end = length
}
n, err := to.write(b[i:end])
if err != nil {
return n, err
}
}
return length, nil
}
func (to *TLSObfs) write(b []byte) (int, error) {
if to.firstRequest { if to.firstRequest {
helloMsg := makeClientHelloMsg(b, to.server) helloMsg := makeClientHelloMsg(b, to.server)
_, err := to.Conn.Write(helloMsg) _, err := to.Conn.Write(helloMsg)
@ -84,7 +102,7 @@ func (to *TLSObfs) Write(b []byte) (int, error) {
return len(b), err return len(b), err
} }
size := bufPool.Get().([]byte) size := pool.BufPool.Get().([]byte)
binary.BigEndian.PutUint16(size[:2], uint16(len(b))) binary.BigEndian.PutUint16(size[:2], uint16(len(b)))
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -92,7 +110,7 @@ func (to *TLSObfs) Write(b []byte) (int, error) {
buf.Write(size[:2]) buf.Write(size[:2])
buf.Write(b) buf.Write(b)
_, err := to.Conn.Write(buf.Bytes()) _, err := to.Conn.Write(buf.Bytes())
bufPool.Put(size[:cap(size)]) pool.BufPool.Put(size[:cap(size)])
return len(b), err return len(b), err
} }
@ -107,9 +125,8 @@ func NewTLSObfs(conn net.Conn, server string) net.Conn {
} }
func makeClientHelloMsg(data []byte, server string) []byte { func makeClientHelloMsg(data []byte, server string) []byte {
random := make([]byte, 32) random := make([]byte, 28)
sessionID := make([]byte, 32) sessionID := make([]byte, 32)
size := make([]byte, 2)
rand.Read(random) rand.Read(random)
rand.Read(sessionID) rand.Read(sessionID)
@ -124,12 +141,12 @@ func makeClientHelloMsg(data []byte, server string) []byte {
// clientHello, length, TLS 1.2 version // clientHello, length, TLS 1.2 version
buf.WriteByte(1) buf.WriteByte(1)
binary.BigEndian.PutUint16(size, uint16(208+len(data)+len(server)))
buf.WriteByte(0) buf.WriteByte(0)
buf.Write(size) binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server)))
buf.Write([]byte{0x03, 0x03}) buf.Write([]byte{0x03, 0x03})
// random, sid len, sid // random with timestamp, sid len, sid
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
buf.Write(random) buf.Write(random)
buf.WriteByte(32) buf.WriteByte(32)
buf.Write(sessionID) buf.Write(sessionID)
@ -147,24 +164,19 @@ func makeClientHelloMsg(data []byte, server string) []byte {
buf.Write([]byte{0x01, 0x00}) buf.Write([]byte{0x01, 0x00})
// extension length // extension length
binary.BigEndian.PutUint16(size, uint16(79+len(data)+len(server))) binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server)))
buf.Write(size)
// session ticket // session ticket
buf.Write([]byte{0x00, 0x23}) buf.Write([]byte{0x00, 0x23})
binary.BigEndian.PutUint16(size, uint16(len(data))) binary.Write(buf, binary.BigEndian, uint16(len(data)))
buf.Write(size)
buf.Write(data) buf.Write(data)
// server name // server name
buf.Write([]byte{0x00, 0x00}) buf.Write([]byte{0x00, 0x00})
binary.BigEndian.PutUint16(size, uint16(len(server)+5)) binary.Write(buf, binary.BigEndian, uint16(len(server)+5))
buf.Write(size) binary.Write(buf, binary.BigEndian, uint16(len(server)+3))
binary.BigEndian.PutUint16(size, uint16(len(server)+3))
buf.Write(size)
buf.WriteByte(0) buf.WriteByte(0)
binary.BigEndian.PutUint16(size, uint16(len(server))) binary.Write(buf, binary.BigEndian, uint16(len(server)))
buf.Write(size)
buf.Write([]byte(server)) buf.Write([]byte(server))
// ec_point // ec_point

247
component/socks5/socks5.go Normal file
View File

@ -0,0 +1,247 @@
package socks5
import (
"bytes"
"errors"
"io"
"net"
"strconv"
)
// Error represents a SOCKS error
type Error byte
func (err Error) Error() string {
return "SOCKS error: " + strconv.Itoa(int(err))
}
// Command is request commands as defined in RFC 1928 section 4.
type Command = uint8
// SOCKS request commands as defined in RFC 1928 section 4.
const (
CmdConnect Command = 1
CmdBind Command = 2
CmdUDPAssociate Command = 3
)
// SOCKS address types as defined in RFC 1928 section 5.
const (
AtypIPv4 = 1
AtypDomainName = 3
AtypIPv6 = 4
)
// MaxAddrLen is the maximum size of SOCKS address in bytes.
const MaxAddrLen = 1 + 1 + 255 + 2
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
type Addr = []byte
// SOCKS errors as defined in RFC 1928 section 6.
const (
ErrGeneralFailure = Error(1)
ErrConnectionNotAllowed = Error(2)
ErrNetworkUnreachable = Error(3)
ErrHostUnreachable = Error(4)
ErrConnectionRefused = Error(5)
ErrTTLExpired = Error(6)
ErrCommandNotSupported = Error(7)
ErrAddressNotSupported = Error(8)
)
type User struct {
Username string
Password string
}
// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) {
// Read RFC 1928 for request and reply structure and sizes.
buf := make([]byte, MaxAddrLen)
// read VER, NMETHODS, METHODS
if _, err = io.ReadFull(rw, buf[:2]); err != nil {
return
}
nmethods := buf[1]
if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
return
}
// write VER METHOD
if _, err = rw.Write([]byte{5, 0}); err != nil {
return
}
// read VER CMD RSV ATYP DST.ADDR DST.PORT
if _, err = io.ReadFull(rw, buf[:3]); err != nil {
return
}
if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate {
err = ErrCommandNotSupported
return
}
command = buf[1]
addr, err = readAddr(rw, buf)
if err != nil {
return
}
// write VER REP RSV ATYP BND.ADDR BND.PORT
_, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
return
}
// ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side.
func ClientHandshake(rw io.ReadWriter, addr Addr, cammand Command, user *User) error {
buf := make([]byte, MaxAddrLen)
var err error
// VER, NMETHODS, METHODS
if user != nil {
_, err = rw.Write([]byte{5, 1, 2})
} else {
_, err = rw.Write([]byte{5, 1, 0})
}
if err != nil {
return err
}
// VER, METHOD
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
return err
}
if buf[0] != 5 {
return errors.New("SOCKS version error")
}
if buf[1] == 2 {
// password protocol version
authMsg := &bytes.Buffer{}
authMsg.WriteByte(1)
authMsg.WriteByte(uint8(len(user.Username)))
authMsg.WriteString(user.Username)
authMsg.WriteByte(uint8(len(user.Password)))
authMsg.WriteString(user.Password)
if _, err := rw.Write(authMsg.Bytes()); err != nil {
return err
}
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
return err
}
if buf[1] != 0 {
return errors.New("rejected username/password")
}
} else if buf[1] != 0 {
return errors.New("SOCKS need auth")
}
// VER, CMD, RSV, ADDR
if _, err := rw.Write(bytes.Join([][]byte{{5, cammand, 0}, addr}, []byte(""))); err != nil {
return err
}
if _, err := io.ReadFull(rw, buf[:10]); err != nil {
return err
}
return nil
}
func readAddr(r io.Reader, b []byte) (Addr, error) {
if len(b) < MaxAddrLen {
return nil, io.ErrShortBuffer
}
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
if err != nil {
return nil, err
}
switch b[0] {
case AtypDomainName:
_, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length
if err != nil {
return nil, err
}
domainLength := uint16(b[1])
_, err = io.ReadFull(r, b[2:2+domainLength+2])
return b[:1+1+domainLength+2], err
case AtypIPv4:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
return b[:1+net.IPv4len+2], err
case AtypIPv6:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
return b[:1+net.IPv6len+2], err
}
return nil, ErrAddressNotSupported
}
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
func SplitAddr(b []byte) Addr {
addrLen := 1
if len(b) < addrLen {
return nil
}
switch b[0] {
case AtypDomainName:
if len(b) < 2 {
return nil
}
addrLen = 1 + 1 + int(b[1]) + 2
case AtypIPv4:
addrLen = 1 + net.IPv4len + 2
case AtypIPv6:
addrLen = 1 + net.IPv6len + 2
default:
return nil
}
if len(b) < addrLen {
return nil
}
return b[:addrLen]
}
// ParseAddr parses the address in string s. Returns nil if failed.
func ParseAddr(s string) Addr {
var addr Addr
host, port, err := net.SplitHostPort(s)
if err != nil {
return nil
}
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
addr = make([]byte, 1+net.IPv4len+2)
addr[0] = AtypIPv4
copy(addr[1:], ip4)
} else {
addr = make([]byte, 1+net.IPv6len+2)
addr[0] = AtypIPv6
copy(addr[1:], ip)
}
} else {
if len(host) > 255 {
return nil
}
addr = make([]byte, 1+1+len(host)+2)
addr[0] = AtypDomainName
addr[1] = byte(len(host))
copy(addr[2:], host)
}
portnum, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return nil
}
addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum)
return addr
}

View File

@ -0,0 +1,173 @@
package obfs
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
)
type SessionStatus = byte
const (
SessionStatusNew SessionStatus = 0x01
SessionStatusKeep SessionStatus = 0x02
SessionStatusEnd SessionStatus = 0x03
SessionStatusKeepAlive SessionStatus = 0x04
)
const (
OptionNone = byte(0x00)
OptionData = byte(0x01)
OptionError = byte(0x02)
)
type MuxOption struct {
ID [2]byte
Port uint16
Host string
Type string
}
// Mux is an mux-compatible client for v2ray-plugin, not a complete implementation
type Mux struct {
net.Conn
buf bytes.Buffer
id [2]byte
length [2]byte
status [2]byte
otb []byte
remain int
}
func (m *Mux) Read(b []byte) (int, error) {
if m.remain != 0 {
length := m.remain
if len(b) < m.remain {
length = len(b)
}
n, err := m.Conn.Read(b[:length])
if err != nil {
return 0, err
}
m.remain = m.remain - n
return n, nil
}
for {
_, err := io.ReadFull(m.Conn, m.length[:])
if err != nil {
return 0, err
}
length := binary.BigEndian.Uint16(m.length[:])
if length > 512 {
return 0, errors.New("invalid metalen")
}
_, err = io.ReadFull(m.Conn, m.id[:])
if err != nil {
return 0, err
}
_, err = m.Conn.Read(m.status[:])
if err != nil {
return 0, err
}
opcode := m.status[0]
if opcode == SessionStatusKeepAlive {
continue
}
opts := m.status[1]
if opts != OptionData {
continue
}
_, err = io.ReadFull(m.Conn, m.length[:])
if err != nil {
return 0, err
}
dataLen := int(binary.BigEndian.Uint16(m.length[:]))
m.remain = dataLen
if dataLen > len(b) {
dataLen = len(b)
}
n, err := m.Conn.Read(b[:dataLen])
m.remain -= n
return n, err
}
}
func (m *Mux) Write(b []byte) (int, error) {
if m.otb != nil {
// create a sub connection
if _, err := m.Conn.Write(m.otb); err != nil {
return 0, err
}
m.otb = nil
}
m.buf.Reset()
binary.Write(&m.buf, binary.BigEndian, uint16(4))
m.buf.Write(m.id[:])
m.buf.WriteByte(SessionStatusKeep)
m.buf.WriteByte(OptionData)
binary.Write(&m.buf, binary.BigEndian, uint16(len(b)))
m.buf.Write(b)
return m.Conn.Write(m.buf.Bytes())
}
func (m *Mux) Close() error {
_, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone})
if err != nil {
return err
}
return m.Conn.Close()
}
func NewMux(conn net.Conn, option MuxOption) *Mux {
buf := &bytes.Buffer{}
// fill empty length
buf.Write([]byte{0x0, 0x0})
buf.Write(option.ID[:])
buf.WriteByte(SessionStatusNew)
buf.WriteByte(OptionNone)
// tcp
netType := byte(0x1)
if option.Type == "udp" {
netType = byte(0x2)
}
buf.WriteByte(netType)
// port
binary.Write(buf, binary.BigEndian, option.Port)
// address
ip := net.ParseIP(option.Host)
if ip == nil {
buf.WriteByte(0x2)
buf.WriteString(option.Host)
} else if ipv4 := ip.To4(); ipv4 != nil {
buf.WriteByte(0x1)
buf.Write(ipv4)
} else {
buf.WriteByte(0x3)
buf.Write(ip.To16())
}
metadata := buf.Bytes()
binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2))
return &Mux{
Conn: conn,
id: option.ID,
otb: metadata,
}
}

View File

@ -0,0 +1,39 @@
package obfs
import (
"crypto/tls"
"net"
"github.com/Dreamacro/clash/component/vmess"
)
// WebsocketOption is options of websocket obfs
type WebsocketOption struct {
Host string
Path string
Headers map[string]string
TLSConfig *tls.Config
}
// NewWebsocketObfs return a HTTPObfs
func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) {
config := &vmess.WebsocketConfig{
Host: option.Host,
Path: option.Path,
TLS: option.TLSConfig != nil,
Headers: option.Headers,
TLSConfig: option.TLSConfig,
}
var err error
conn, err = vmess.NewWebsocketConn(conn, config)
if err != nil {
return nil, err
}
conn = NewMux(conn, MuxOption{
ID: [2]byte{0, 0},
Host: "127.0.0.1",
Port: 0,
})
return conn, nil
}

View File

@ -5,6 +5,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"github.com/Dreamacro/clash/common/pool"
) )
type aeadWriter struct { type aeadWriter struct {
@ -20,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 := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
defer bufPool.Put(buf[:cap(buf)]) defer pool.BufPool.Put(buf[:cap(buf)])
length := len(b) length := len(b)
for { for {
if length == 0 { if length == 0 {
@ -71,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) {
bufPool.Put(r.buf[:cap(r.buf)]) pool.BufPool.Put(r.buf[:cap(r.buf)])
r.buf = nil r.buf = nil
} }
return n, nil return n, nil
@ -87,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 := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err = io.ReadFull(r.Reader, buf[:size]) _, err = io.ReadFull(r.Reader, buf[:size])
if err != nil { if err != nil {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, err return 0, err
} }
@ -105,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 {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return n, nil return n, nil
} }

View File

@ -4,7 +4,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"sync"
"github.com/Dreamacro/clash/common/pool"
) )
const ( const (
@ -13,8 +14,6 @@ const (
maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead()
) )
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, maxSize) }}
type chunkReader struct { type chunkReader struct {
io.Reader io.Reader
buf []byte buf []byte
@ -35,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) {
bufPool.Put(cr.buf[:cap(cr.buf)]) pool.BufPool.Put(cr.buf[:cap(cr.buf)])
cr.buf = nil cr.buf = nil
} }
return n, nil return n, nil
@ -60,10 +59,10 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
return size, nil return size, nil
} }
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err = io.ReadFull(cr.Reader, buf[:size]) _, err = io.ReadFull(cr.Reader, buf[:size])
if err != nil { if err != nil {
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
return 0, err return 0, err
} }
n := copy(b, cr.buf[:]) n := copy(b, cr.buf[:])
@ -77,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 := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
defer bufPool.Put(buf[:cap(buf)]) defer pool.BufPool.Put(buf[:cap(buf)])
length := len(b) length := len(b)
for { for {
if length == 0 { if length == 0 {

View File

@ -35,20 +35,10 @@ type Conn struct {
respV byte respV byte
security byte security byte
sent bool
received bool received bool
} }
func (vc *Conn) Write(b []byte) (int, error) { func (vc *Conn) Write(b []byte) (int, error) {
if vc.sent {
return vc.writer.Write(b)
}
if err := vc.sendRequest(); err != nil {
return 0, err
}
vc.sent = true
return vc.writer.Write(b) return vc.writer.Write(b)
} }
@ -87,7 +77,11 @@ func (vc *Conn) sendRequest() error {
// P Sec Reserve Cmd // P Sec Reserve Cmd
buf.WriteByte(byte(p<<4) | byte(vc.security)) buf.WriteByte(byte(p<<4) | byte(vc.security))
buf.WriteByte(0) buf.WriteByte(0)
buf.WriteByte(CommandTCP) if vc.dst.UDP {
buf.WriteByte(CommandUDP)
} else {
buf.WriteByte(CommandTCP)
}
// Port AddrType Addr // Port AddrType Addr
binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port))
@ -153,7 +147,7 @@ func hashTimestamp(t time.Time) []byte {
} }
// newConn return a Conn instance // newConn return a Conn instance
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn { func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, error) {
randBytes := make([]byte, 33) randBytes := make([]byte, 33)
rand.Read(randBytes) rand.Read(randBytes)
reqBodyIV := make([]byte, 16) reqBodyIV := make([]byte, 16)
@ -196,7 +190,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
reader = newAEADReader(conn, aead, respBodyIV[:]) reader = newAEADReader(conn, aead, respBodyIV[:])
} }
return &Conn{ c := &Conn{
Conn: conn, Conn: conn,
id: id, id: id,
dst: dst, dst: dst,
@ -209,4 +203,8 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
writer: writer, writer: writer,
security: security, security: security,
} }
if err := c.sendRequest(); err != nil {
return nil, err
}
return c, nil
} }

View File

@ -57,6 +57,7 @@ const (
// DstAddr store destination address // DstAddr store destination address
type DstAddr struct { type DstAddr struct {
UDP bool
AddrType byte AddrType byte
Addr []byte Addr []byte
Port uint Port uint
@ -69,21 +70,23 @@ type Client struct {
security Security security Security
tls bool tls bool
host string host string
wsConfig *websocketConfig wsConfig *WebsocketConfig
tlsConfig *tls.Config tlsConfig *tls.Config
} }
// Config of vmess // Config of vmess
type Config struct { type Config struct {
UUID string UUID string
AlterID uint16 AlterID uint16
Security string Security string
TLS bool TLS bool
Host string HostName string
NetWork string Port string
WebSocketPath string NetWork string
SkipCertVerify bool WebSocketPath string
SessionCacahe tls.ClientSessionCache WebSocketHeaders map[string]string
SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
} }
// New return a Conn with net.Conn and DstAddr // New return a Conn with net.Conn and DstAddr
@ -91,14 +94,14 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error var err error
r := rand.Intn(len(c.user)) r := rand.Intn(len(c.user))
if c.wsConfig != nil { if c.wsConfig != nil {
conn, err = newWebsocketConn(conn, c.wsConfig) conn, err = NewWebsocketConn(conn, c.wsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if c.tls { } else if c.tls {
conn = tls.Client(conn, c.tlsConfig) conn = tls.Client(conn, c.tlsConfig)
} }
return newConn(conn, c.user[r], dst, c.security), nil return newConn(conn, c.user[r], dst, c.security)
} }
// NewClient return Client instance // NewClient return Client instance
@ -129,9 +132,12 @@ func NewClient(config Config) (*Client, error) {
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
} }
host := net.JoinHostPort(config.HostName, config.Port)
var tlsConfig *tls.Config var tlsConfig *tls.Config
if config.TLS { if config.TLS {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
ServerName: config.HostName,
InsecureSkipVerify: config.SkipCertVerify, InsecureSkipVerify: config.SkipCertVerify,
ClientSessionCache: config.SessionCacahe, ClientSessionCache: config.SessionCacahe,
} }
@ -140,13 +146,14 @@ func NewClient(config Config) (*Client, error) {
} }
} }
var wsConfig *websocketConfig var wsConfig *WebsocketConfig
if config.NetWork == "ws" { if config.NetWork == "ws" {
wsConfig = &websocketConfig{ wsConfig = &WebsocketConfig{
host: config.Host, Host: host,
path: config.WebSocketPath, Path: config.WebSocketPath,
tls: config.TLS, Headers: config.WebSocketHeaders,
tlsConfig: tlsConfig, TLS: config.TLS,
TLSConfig: tlsConfig,
} }
} }
@ -155,7 +162,7 @@ func NewClient(config Config) (*Client, error) {
uuid: &uid, uuid: &uid,
security: security, security: security,
tls: config.TLS, tls: config.TLS,
host: config.Host, host: host,
wsConfig: wsConfig, wsConfig: wsConfig,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
}, nil }, nil

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -18,11 +19,12 @@ type websocketConn struct {
remoteAddr net.Addr remoteAddr net.Addr
} }
type websocketConfig struct { type WebsocketConfig struct {
host string Host string
path string Path string
tls bool Headers map[string]string
tlsConfig *tls.Config TLS bool
TLSConfig *tls.Config
} }
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
@ -100,7 +102,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 NewWebsocketConn(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
@ -111,23 +113,30 @@ 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 = c.TLSConfig
} }
host, port, err := net.SplitHostPort(c.host) host, port, _ := net.SplitHostPort(c.Host)
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
host = c.host host = c.Host
} }
uri := url.URL{ uri := url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: host,
Path: c.path, Path: c.Path,
} }
wsConn, resp, err := dialer.Dial(uri.String(), nil) headers := http.Header{}
if c.Headers != nil {
for k, v := range c.Headers {
headers.Set(k, v)
}
}
wsConn, resp, err := dialer.Dial(uri.String(), headers)
if err != nil { if err != nil {
var reason string var reason string
if resp != nil { if resp != nil {

View File

@ -3,94 +3,95 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"net/url"
"os" "os"
"path/filepath"
"strings" "strings"
"sync"
"time"
adapters "github.com/Dreamacro/clash/adapters/outbound" adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/common/observable"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rules" R "github.com/Dreamacro/clash/rules"
T "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
var (
config *Config
once sync.Once
)
// General config // General config
type General struct { type General struct {
Port int Port int `json:"port"`
SocksPort int SocksPort int `json:"socks-port"`
RedirPort int RedirPort int `json:"redir-port"`
AllowLan bool AllowLan bool `json:"allow-lan"`
Mode Mode Mode T.Mode `json:"mode"`
LogLevel C.LogLevel LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"-"`
ExternalUI string `json:"-"`
Secret string `json:"-"`
} }
// ProxyConfig is update proxy schema // DNS config
type ProxyConfig struct { type DNS struct {
Port *int Enable bool `yaml:"enable"`
SocksPort *int IPv6 bool `yaml:"ipv6"`
RedirPort *int NameServer []dns.NameServer `yaml:"nameserver"`
AllowLan *bool Fallback []dns.NameServer `yaml:"fallback"`
Listen string `yaml:"listen"`
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
FakeIPRange *fakeip.Pool
} }
// RawConfig is raw config struct // Experimental config
type RawConfig struct { type Experimental struct {
Port int `yaml:"port"` IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"`
AllowLan bool `yaml:"allow-lan"`
Mode string `yaml:"mode"`
LogLevel string `yaml:"log-level"`
ExternalController string `yaml:"external-controller"`
Secret string `yaml:"secret"`
Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"`
} }
// Config is clash config manager // Config is clash config manager
type Config struct { type Config struct {
general *General General *General
rules []C.Rule DNS *DNS
proxies map[string]C.Proxy Experimental *Experimental
lastUpdate time.Time Rules []C.Rule
Proxies map[string]C.Proxy
event chan<- interface{}
reportCh chan interface{}
observable *observable.Observable
} }
// Event is event of clash config type rawDNS struct {
type Event struct { Enable bool `yaml:"enable"`
Type string IPv6 bool `yaml:"ipv6"`
Payload interface{} NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
Listen string `yaml:"listen"`
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
} }
// Subscribe config stream type rawConfig struct {
func (c *Config) Subscribe() observable.Subscription { Port int `yaml:"port"`
sub, _ := c.observable.Subscribe() SocksPort int `yaml:"socks-port"`
return sub RedirPort int `yaml:"redir-port"`
AllowLan bool `yaml:"allow-lan"`
Mode T.Mode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"`
ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"`
DNS rawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"`
Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"`
} }
// Report return a channel for collecting report message func readConfig(path string) (*rawConfig, error) {
func (c *Config) Report() chan<- interface{} { if _, err := os.Stat(path); os.IsNotExist(err) {
return c.reportCh
}
func (c *Config) readConfig() (*RawConfig, error) {
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
return nil, err return nil, err
} }
data, err := ioutil.ReadFile(C.Path.Config()) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,162 +101,116 @@ func (c *Config) readConfig() (*RawConfig, error) {
} }
// config with some default value // config with some default value
rawConfig := &RawConfig{ rawConfig := &rawConfig{
AllowLan: false, AllowLan: false,
Mode: Rule.String(), Mode: T.Rule,
LogLevel: C.INFO.String(), LogLevel: log.INFO,
Rule: []string{}, Rule: []string{},
Proxy: []map[string]interface{}{}, Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{},
Experimental: Experimental{
IgnoreResolveFail: true,
},
DNS: rawDNS{
Enable: false,
FakeIPRange: "198.18.0.1/16",
},
} }
err = yaml.Unmarshal([]byte(data), &rawConfig) err = yaml.Unmarshal([]byte(data), &rawConfig)
return rawConfig, err return rawConfig, err
} }
// Parse config // Parse config
func (c *Config) Parse() error { func Parse(path string) (*Config, error) {
cfg, err := c.readConfig() config := &Config{}
rawCfg, err := readConfig(path)
if err != nil { if err != nil {
return err return nil, err
} }
config.Experimental = &rawCfg.Experimental
if err := c.parseGeneral(cfg); err != nil { general, err := parseGeneral(rawCfg)
return err
}
if err := c.parseProxies(cfg); err != nil {
return err
}
return c.parseRules(cfg)
}
// Proxies return proxies of clash
func (c *Config) Proxies() map[string]C.Proxy {
return c.proxies
}
// Rules return rules of clash
func (c *Config) Rules() []C.Rule {
return c.rules
}
// SetMode change mode of clash
func (c *Config) SetMode(mode Mode) {
c.general.Mode = mode
c.event <- &Event{Type: "mode", Payload: mode}
}
// SetLogLevel change log level of clash
func (c *Config) SetLogLevel(level C.LogLevel) {
c.general.LogLevel = level
c.event <- &Event{Type: "log-level", Payload: level}
}
// General return clash general config
func (c *Config) General() General {
return *c.general
}
// UpdateRules is a function for hot reload rules
func (c *Config) UpdateRules() error {
cfg, err := c.readConfig()
if err != nil { if err != nil {
return err return nil, err
} }
config.General = general
return c.parseRules(cfg) proxies, err := parseProxies(rawCfg)
if err != nil {
return nil, err
}
config.Proxies = proxies
rules, err := parseRules(rawCfg)
if err != nil {
return nil, err
}
config.Rules = rules
dnsCfg, err := parseDNS(rawCfg.DNS)
if err != nil {
return nil, err
}
config.DNS = dnsCfg
return config, nil
} }
func (c *Config) parseGeneral(cfg *RawConfig) 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
allowLan := cfg.AllowLan allowLan := cfg.AllowLan
logLevelString := cfg.LogLevel externalController := cfg.ExternalController
modeString := cfg.Mode externalUI := cfg.ExternalUI
secret := cfg.Secret
mode := cfg.Mode
logLevel := cfg.LogLevel
mode, exist := ModeMapping[modeString] if externalUI != "" {
if !exist { if !filepath.IsAbs(externalUI) {
return fmt.Errorf("General.mode value invalid") externalUI = filepath.Join(C.Path.HomeDir(), externalUI)
}
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
}
} }
logLevel, exist := C.LogLevelMapping[logLevelString] general := &General{
if !exist { Port: port,
return fmt.Errorf("General.log-level value invalid") SocksPort: socksPort,
RedirPort: redirPort,
AllowLan: allowLan,
Mode: mode,
LogLevel: logLevel,
ExternalController: externalController,
ExternalUI: externalUI,
Secret: secret,
} }
return general, nil
c.general = &General{
Port: port,
SocksPort: socksPort,
RedirPort: redirPort,
AllowLan: allowLan,
Mode: mode,
LogLevel: logLevel,
}
if restAddr := cfg.ExternalController; restAddr != "" {
c.event <- &Event{Type: "external-controller", Payload: restAddr}
c.event <- &Event{Type: "secret", Payload: cfg.Secret}
}
c.UpdateGeneral(*c.general)
return nil
} }
// UpdateGeneral dispatch update event func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
func (c *Config) UpdateGeneral(general General) {
c.UpdateProxy(ProxyConfig{
Port: &general.Port,
SocksPort: &general.SocksPort,
RedirPort: &general.RedirPort,
AllowLan: &general.AllowLan,
})
c.event <- &Event{Type: "mode", Payload: general.Mode}
c.event <- &Event{Type: "log-level", Payload: general.LogLevel}
}
// UpdateProxy dispatch update proxy event
func (c *Config) UpdateProxy(pc ProxyConfig) {
if pc.AllowLan != nil {
c.general.AllowLan = *pc.AllowLan
}
c.general.Port = *or(pc.Port, &c.general.Port)
if c.general.Port != 0 && (pc.AllowLan != nil || pc.Port != nil) {
c.event <- &Event{Type: "http-addr", Payload: genAddr(c.general.Port, c.general.AllowLan)}
}
c.general.SocksPort = *or(pc.SocksPort, &c.general.SocksPort)
if c.general.SocksPort != 0 && (pc.AllowLan != nil || pc.SocksPort != nil) {
c.event <- &Event{Type: "socks-addr", Payload: genAddr(c.general.SocksPort, c.general.AllowLan)}
}
c.general.RedirPort = *or(pc.RedirPort, &c.general.RedirPort)
if c.general.RedirPort != 0 && (pc.AllowLan != nil || pc.RedirPort != nil) {
c.event <- &Event{Type: "redir-addr", Payload: genAddr(c.general.RedirPort, c.general.AllowLan)}
}
}
func (c *Config) parseProxies(cfg *RawConfig) error {
proxies := make(map[string]C.Proxy) proxies := make(map[string]C.Proxy)
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
proxies["DIRECT"] = adapters.NewDirect() proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
proxies["REJECT"] = adapters.NewReject() proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
proxyType, existType := mapping["type"].(string) proxyType, existType := mapping["type"].(string)
if !existType { if !existType {
return fmt.Errorf("Proxy %d missing type", idx) return nil, fmt.Errorf("Proxy %d missing type", idx)
} }
var proxy C.Proxy var proxy C.ProxyAdapter
var err error err := fmt.Errorf("can't parse")
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &adapters.ShadowSocksOption{} ssOption := &adapters.ShadowSocksOption{}
@ -271,6 +226,13 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
break break
} }
proxy = adapters.NewSocks5(*socksOption) proxy = adapters.NewSocks5(*socksOption)
case "http":
httpOption := &adapters.HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy = adapters.NewHttp(*httpOption)
case "vmess": case "vmess":
vmessOption := &adapters.VmessOption{} vmessOption := &adapters.VmessOption{}
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
@ -279,17 +241,17 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
} }
proxy, err = adapters.NewVmess(*vmessOption) proxy, err = adapters.NewVmess(*vmessOption)
default: default:
return fmt.Errorf("Unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
} }
if err != nil { if err != nil {
return fmt.Errorf("Proxy [%d]: %s", idx, err.Error()) return nil, fmt.Errorf("Proxy [%d]: %s", idx, err.Error())
} }
if _, exist := proxies[proxy.Name()]; exist { if _, exist := proxies[proxy.Name()]; exist {
return fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
} }
proxies[proxy.Name()] = proxy proxies[proxy.Name()] = adapters.NewProxy(proxy)
} }
// parse proxy group // parse proxy group
@ -297,14 +259,16 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
groupType, existType := mapping["type"].(string) groupType, existType := mapping["type"].(string)
groupName, existName := mapping["name"].(string) groupName, existName := mapping["name"].(string)
if !existType && existName { if !existType && existName {
return fmt.Errorf("ProxyGroup %d: missing type or name", idx) return nil, fmt.Errorf("ProxyGroup %d: missing type or name", idx)
} }
if _, exist := proxies[groupName]; exist { if _, exist := proxies[groupName]; exist {
return fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
} }
var group C.Proxy var group C.ProxyAdapter
var err error ps := []C.Proxy{}
err := fmt.Errorf("can't parse")
switch groupType { switch groupType {
case "url-test": case "url-test":
urlTestOption := &adapters.URLTestOption{} urlTestOption := &adapters.URLTestOption{}
@ -313,9 +277,9 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
break break
} }
ps, err := getProxies(proxies, urlTestOption.Proxies) ps, err = getProxies(proxies, urlTestOption.Proxies)
if err != nil { if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewURLTest(*urlTestOption, ps) group, err = adapters.NewURLTest(*urlTestOption, ps)
case "select": case "select":
@ -325,9 +289,9 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
break break
} }
ps, err := getProxies(proxies, selectorOption.Proxies) ps, err = getProxies(proxies, selectorOption.Proxies)
if err != nil { if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewSelector(selectorOption.Name, ps) group, err = adapters.NewSelector(selectorOption.Name, ps)
case "fallback": case "fallback":
@ -337,115 +301,199 @@ func (c *Config) parseProxies(cfg *RawConfig) error {
break break
} }
ps, err := getProxies(proxies, fallbackOption.Proxies) ps, err = getProxies(proxies, fallbackOption.Proxies)
if err != nil { if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewFallback(*fallbackOption, ps) group, err = adapters.NewFallback(*fallbackOption, ps)
case "load-balance":
loadBalanceOption := &adapters.LoadBalanceOption{}
err = decoder.Decode(mapping, loadBalanceOption)
if err != nil {
break
}
ps, err = getProxies(proxies, loadBalanceOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
group, err = adapters.NewLoadBalance(*loadBalanceOption, ps)
} }
if err != nil { if err != nil {
return fmt.Errorf("Proxy %s: %s", groupName, err.Error()) return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
} }
proxies[groupName] = group proxies[groupName] = adapters.NewProxy(group)
} }
var ps []C.Proxy ps := []C.Proxy{}
for _, v := range proxies { for _, v := range proxies {
ps = append(ps, v) ps = append(ps, v)
} }
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) global, _ := adapters.NewSelector("GLOBAL", ps)
proxies["GLOBAL"] = adapters.NewProxy(global)
// close old goroutine return proxies, nil
for _, proxy := range c.proxies {
switch raw := proxy.(type) {
case *adapters.URLTest:
raw.Close()
case *adapters.Fallback:
raw.Close()
}
}
c.proxies = proxies
c.event <- &Event{Type: "proxies", Payload: proxies}
return nil
} }
func (c *Config) parseRules(cfg *RawConfig) error { func parseRules(cfg *rawConfig) ([]C.Rule, error) {
rules := []C.Rule{} rules := []C.Rule{}
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
// parse rules // parse rules
for _, line := range rulesConfig { for idx, line := range rulesConfig {
rule := strings.Split(line, ",") rule := trimArr(strings.Split(line, ","))
if len(rule) < 3 { var (
continue payload string
target string
)
switch len(rule) {
case 2:
target = rule[1]
case 3:
payload = rule[1]
target = rule[2]
default:
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
} }
rule = trimArr(rule) rule = trimArr(rule)
var parsed C.Rule
switch rule[0] { switch rule[0] {
case "DOMAIN": case "DOMAIN":
rules = append(rules, R.NewDomain(rule[1], rule[2])) parsed = R.NewDomain(payload, target)
case "DOMAIN-SUFFIX": case "DOMAIN-SUFFIX":
rules = append(rules, R.NewDomainSuffix(rule[1], rule[2])) parsed = R.NewDomainSuffix(payload, target)
case "DOMAIN-KEYWORD": case "DOMAIN-KEYWORD":
rules = append(rules, R.NewDomainKeyword(rule[1], rule[2])) parsed = R.NewDomainKeyword(payload, target)
case "GEOIP": case "GEOIP":
rules = append(rules, R.NewGEOIP(rule[1], rule[2])) parsed = R.NewGEOIP(payload, target)
case "IP-CIDR", "IP-CIDR6": case "IP-CIDR", "IP-CIDR6":
rules = append(rules, R.NewIPCIDR(rule[1], rule[2])) if rule := R.NewIPCIDR(payload, target, false); rule != nil {
parsed = rule
}
// deprecated when bump to 1.0
case "SOURCE-IP-CIDR":
fallthrough
case "SRC-IP-CIDR":
if rule := R.NewIPCIDR(payload, target, true); rule != nil {
parsed = rule
}
case "SRC-PORT":
if rule := R.NewPort(payload, target, true); rule != nil {
parsed = rule
}
case "DST-PORT":
if rule := R.NewPort(payload, target, false); rule != nil {
parsed = rule
}
case "MATCH":
fallthrough
// deprecated when bump to 1.0
case "FINAL": case "FINAL":
rules = append(rules, R.NewFinal(rule[2])) parsed = R.NewMatch(target)
} }
}
c.rules = rules if parsed == nil {
c.event <- &Event{Type: "rules", Payload: rules} return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line)
return nil
}
func (c *Config) handleResponseMessage() {
for elm := range c.reportCh {
event := elm.(*Event)
switch event.Type {
case "http-addr":
if event.Payload.(bool) == false {
log.Errorf("Listening HTTP proxy at %d error", c.general.Port)
c.general.Port = 0
}
case "socks-addr":
if event.Payload.(bool) == false {
log.Errorf("Listening SOCKS proxy at %d error", c.general.SocksPort)
c.general.SocksPort = 0
}
case "redir-addr":
if event.Payload.(bool) == false {
log.Errorf("Listening Redir proxy at %d error", c.general.RedirPort)
c.general.RedirPort = 0
}
} }
rules = append(rules, parsed)
} }
return rules, nil
} }
func newConfig() *Config { func hostWithDefaultPort(host string, defPort string) (string, error) {
event := make(chan interface{}) if !strings.Contains(host, ":") {
reportCh := make(chan interface{}) host += ":"
config := &Config{
general: &General{},
proxies: make(map[string]C.Proxy),
rules: []C.Rule{},
lastUpdate: time.Now(),
event: event,
reportCh: reportCh,
observable: observable.NewObservable(event),
} }
go config.handleResponseMessage()
return config hostname, port, err := net.SplitHostPort(host)
if err != nil {
return "", err
}
if port == "" {
port = defPort
}
return net.JoinHostPort(hostname, port), nil
} }
// Instance return singleton instance of Config func parseNameServer(servers []string) ([]dns.NameServer, error) {
func Instance() *Config { nameservers := []dns.NameServer{}
once.Do(func() {
config = newConfig() for idx, server := range servers {
}) // parse without scheme .e.g 8.8.8.8:53
return config if !strings.Contains(server, "://") {
server = "udp://" + server
}
u, err := url.Parse(server)
if err != nil {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
}
var host, dnsNetType string
switch u.Scheme {
case "udp":
host, err = hostWithDefaultPort(u.Host, "53")
dnsNetType = "" // UDP
case "tcp":
host, err = hostWithDefaultPort(u.Host, "53")
dnsNetType = "tcp" // TCP
case "tls":
host, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS
default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
}
if err != nil {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
}
nameservers = append(
nameservers,
dns.NameServer{
Net: dnsNetType,
Addr: host,
},
)
}
return nameservers, nil
}
func parseDNS(cfg rawDNS) (*DNS, error) {
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
}
dnsCfg := &DNS{
Enable: cfg.Enable,
Listen: cfg.Listen,
EnhancedMode: cfg.EnhancedMode,
}
var err error
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
return nil, err
}
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback); err != nil {
return nil, err
}
if cfg.EnhancedMode == dns.FAKEIP {
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
if err != nil {
return nil, err
}
pool, err := fakeip.New(ipnet)
if err != nil {
return nil, err
}
dnsCfg.FakeIPRange = pool
}
return dnsCfg, nil
} }

View File

@ -3,6 +3,7 @@ package config
import ( import (
"archive/tar" "archive/tar"
"compress/gzip" "compress/gzip"
"fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -54,17 +55,17 @@ func downloadMMDB(path string) (err error) {
} }
// Init prepare necessary files // Init prepare necessary files
func Init() { func Init(dir string) error {
// initial homedir // initial homedir
if _, err := os.Stat(C.Path.HomeDir()); os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(C.Path.HomeDir(), 0777); err != nil { if err := os.MkdirAll(dir, 0777); err != nil {
log.Fatalf("Can't create config directory %s: %s", C.Path.HomeDir(), err.Error()) return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error())
} }
} }
// initial config.ini // initial config.yml
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
log.Info("Can't find config, create a empty file") log.Info("Can't find config, create an empty file")
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
} }
@ -73,7 +74,8 @@ func Init() {
log.Info("Can't find MMDB, start download") log.Info("Can't find MMDB, start download")
err := downloadMMDB(C.Path.MMDB()) err := downloadMMDB(C.Path.MMDB())
if err != nil { if err != nil {
log.Fatalf("Can't download MMDB: %s", err.Error()) return fmt.Errorf("Can't download MMDB: %s", err.Error())
} }
} }
return nil
} }

View File

@ -1,31 +0,0 @@
package config
type Mode int
var (
// ModeMapping is a mapping for Mode enum
ModeMapping = map[string]Mode{
"Global": Global,
"Rule": Rule,
"Direct": Direct,
}
)
const (
Global Mode = iota
Rule
Direct
)
func (m Mode) String() string {
switch m {
case Global:
return "Global"
case Rule:
return "Rule"
case Direct:
return "Direct"
default:
return "Unknow"
}
}

View File

@ -14,13 +14,6 @@ func trimArr(arr []string) (r []string) {
return return
} }
func genAddr(port int, allowLan bool) string {
if allowLan {
return fmt.Sprintf(":%d", port)
}
return fmt.Sprintf("127.0.0.1:%d", port)
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy var ps []C.Proxy
for _, name := range list { for _, name := range list {

View File

@ -2,6 +2,7 @@ package constant
import ( import (
"net" "net"
"time"
) )
// Adapter Type // Adapter Type
@ -12,24 +13,38 @@ const (
Selector Selector
Shadowsocks Shadowsocks
Socks5 Socks5
Http
URLTest URLTest
Vmess Vmess
LoadBalance
) )
type ProxyAdapter interface { type ServerAdapter interface {
Conn() net.Conn net.Conn
Close() Metadata() *Metadata
} }
type ServerAdapter interface { type ProxyAdapter interface {
Metadata() *Metadata Name() string
Close() Type() AdapterType
Dial(metadata *Metadata) (net.Conn, error)
DialUDP(metadata *Metadata) (net.PacketConn, net.Addr, error)
SupportUDP() bool
Destroy()
MarshalJSON() ([]byte, error)
}
type DelayHistory struct {
Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
} }
type Proxy interface { type Proxy interface {
Name() string ProxyAdapter
Type() AdapterType Alive() bool
Generator(metadata *Metadata) (ProxyAdapter, error) DelayHistory() []DelayHistory
LastDelay() uint16
URLTest(url string) (uint16, error)
} }
// AdapterType is enum of adapter type // AdapterType is enum of adapter type
@ -49,10 +64,14 @@ func (at AdapterType) String() string {
return "Shadowsocks" return "Shadowsocks"
case Socks5: case Socks5:
return "Socks5" return "Socks5"
case Http:
return "Http"
case URLTest: case URLTest:
return "URLTest" return "URLTest"
case Vmess: case Vmess:
return "Vmess" return "Vmess"
case LoadBalance:
return "LoadBalance"
default: default:
return "Unknow" return "Unknow"
} }

View File

@ -1,10 +0,0 @@
package constant
type General struct {
Mode *string `json:"mode,omitempty"`
AllowLan *bool `json:"allow-lan,omitempty"`
Port *int `json:"port,omitempty"`
SocksPort *int `json:"socks-port,omitempty"`
RedirPort *int `json:"redir-port,omitempty"`
LogLevel *string `json:"log-level,omitempty"`
}

View File

@ -1,35 +0,0 @@
package constant
var (
// LogLevelMapping is a mapping for LogLevel enum
LogLevelMapping = map[string]LogLevel{
"error": ERROR,
"warning": WARNING,
"info": INFO,
"debug": DEBUG,
}
)
const (
ERROR LogLevel = iota
WARNING
INFO
DEBUG
)
type LogLevel int
func (l LogLevel) String() string {
switch l {
case INFO:
return "info"
case WARNING:
return "warning"
case ERROR:
return "error"
case DEBUG:
return "debug"
default:
return "unknow"
}
}

View File

@ -13,8 +13,9 @@ const (
TCP NetWork = iota TCP NetWork = iota
UDP UDP
HTTP SourceType = iota HTTP Type = iota
SOCKS SOCKS
REDIR
) )
type NetWork int type NetWork int
@ -26,21 +27,27 @@ func (n *NetWork) String() string {
return "udp" return "udp"
} }
type SourceType int type Type int
// Metadata is used to store connection address // Metadata is used to store connection address
type Metadata struct { type Metadata struct {
NetWork NetWork NetWork NetWork
Source SourceType Type Type
SrcIP *net.IP
DstIP *net.IP
SrcPort string
DstPort string
AddrType int AddrType int
Host string Host string
IP *net.IP
Port string
} }
func (addr *Metadata) String() string { func (m *Metadata) String() string {
if addr.Host == "" { if m.Host == "" {
return addr.IP.String() return m.DstIP.String()
} }
return addr.Host return m.Host
}
func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP != nil
} }

View File

@ -27,6 +27,7 @@ func init() {
} else { } else {
homedir = currentUser.HomeDir homedir = currentUser.HomeDir
} }
homedir = P.Join(homedir, ".config", Name) homedir = P.Join(homedir, ".config", Name)
Path = &path{homedir: homedir} Path = &path{homedir: homedir}
} }

View File

@ -1,7 +0,0 @@
package constant
// ProxySignal is used to handle graceful shutdown of proxy
type ProxySignal struct {
Done chan<- struct{}
Closed <-chan struct{}
}

View File

@ -7,7 +7,10 @@ const (
DomainKeyword DomainKeyword
GEOIP GEOIP
IPCIDR IPCIDR
FINAL SrcIPCIDR
SrcPort
DstPort
MATCH
) )
type RuleType int type RuleType int
@ -24,8 +27,14 @@ func (rt RuleType) String() string {
return "GEOIP" return "GEOIP"
case IPCIDR: case IPCIDR:
return "IPCIDR" return "IPCIDR"
case FINAL: case SrcIPCIDR:
return "FINAL" return "SrcIPCIDR"
case SrcPort:
return "SrcPort"
case DstPort:
return "DstPort"
case MATCH:
return "MATCH"
default: default:
return "Unknow" return "Unknow"
} }

6
constant/version.go Normal file
View File

@ -0,0 +1,6 @@
package constant
var (
Version = "unknown version"
BuildTime = "unknown time"
)

270
dns/client.go Normal file
View File

@ -0,0 +1,270 @@
package dns
import (
"context"
"crypto/tls"
"errors"
"net"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker"
"github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
)
var (
globalSessionCache = tls.NewLRUClientSessionCache(64)
mmdb *geoip2.Reader
once sync.Once
resolver *Resolver
)
type Resolver struct {
ipv6 bool
mapping bool
fakeip bool
pool *fakeip.Pool
fallback []*nameserver
main []*nameserver
cache *cache.Cache
}
type result struct {
Msg *D.Msg
Error error
}
func isIPRequest(q D.Question) bool {
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
return true
}
return false
}
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 {
return nil, errors.New("should have one question at least")
}
q := m.Question[0]
cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil {
msg = cache.(*D.Msg).Copy()
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
return
}
defer func() {
if msg == nil {
return
}
putMsgToCache(r.cache, q.String(), msg)
if r.mapping {
ips := r.msgToIP(msg)
for _, ip := range ips {
putMsgToCache(r.cache, ip.String(), msg)
}
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
msg, err = r.resolveIP(m)
return
}
msg, err = r.exchange(r.main, m)
return
}
func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err error) {
in := make(chan interface{})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
fast := picker.SelectFast(ctx, in)
wg := sync.WaitGroup{}
wg.Add(len(servers))
for _, server := range servers {
go func(s *nameserver) {
defer wg.Done()
msg, _, err := s.Client.Exchange(m, s.Address)
if err != nil || msg.Rcode != D.RcodeSuccess {
return
}
in <- &result{Msg: msg, Error: err}
}(server)
}
// release in channel
go func() {
wg.Wait()
close(in)
}()
elm, exist := <-fast
if !exist {
return nil, errors.New("All DNS requests failed")
}
resp := elm.(*result)
msg, err = resp.Msg, resp.Error
return
}
func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
msgCh := r.resolve(r.main, m)
if r.fallback == nil {
res := <-msgCh
msg, err = res.Msg, res.Error
return
}
fallbackMsg := r.resolve(r.fallback, m)
res := <-msgCh
if res.Error == nil {
if mmdb == nil {
return nil, errors.New("GeoIP can't use")
}
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
// release channel
go func() { <-fallbackMsg }()
msg = res.Msg
return msg, err
}
}
}
res = <-fallbackMsg
msg, err = res.Msg, res.Error
return
}
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ip = net.ParseIP(host)
if ip != nil {
return ip, nil
}
query := &D.Msg{}
dnsType := D.TypeA
if r.ipv6 {
dnsType = D.TypeAAAA
}
query.SetQuestion(D.Fqdn(host), dnsType)
msg, err := r.Exchange(query)
if err != nil {
return nil, err
}
ips := r.msgToIP(msg)
if len(ips) == 0 {
return nil, errors.New("can't found ip")
}
ip = ips[0]
return
}
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
ips := []net.IP{}
for _, answer := range msg.Answer {
switch ans := answer.(type) {
case *D.AAAA:
ips = append(ips, ans.AAAA)
case *D.A:
ips = append(ips, ans.A)
}
}
return ips
}
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
cache := r.cache.Get(ip.String())
if cache == nil {
return "", false
}
fqdn := cache.(*D.Msg).Question[0].Name
return strings.TrimRight(fqdn, "."), true
}
func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result {
ch := make(chan *result)
go func() {
res, err := r.exchange(client, msg)
ch <- &result{Msg: res, Error: err}
}()
return ch
}
func (r *Resolver) IsMapping() bool {
return r.mapping
}
func (r *Resolver) IsFakeIP() bool {
return r.fakeip
}
type NameServer struct {
Net string
Addr string
}
type nameserver struct {
Client *D.Client
Address string
}
type Config struct {
Main, Fallback []NameServer
IPv6 bool
EnhancedMode EnhancedMode
Pool *fakeip.Pool
}
func transform(servers []NameServer) []*nameserver {
var ret []*nameserver
for _, s := range servers {
ret = append(ret, &nameserver{
Client: &D.Client{
Net: s.Net,
TLSConfig: &tls.Config{
ClientSessionCache: globalSessionCache,
},
UDPSize: 4096,
},
Address: s.Addr,
})
}
return ret
}
func New(config Config) *Resolver {
once.Do(func() {
mmdb, _ = geoip2.Open(C.Path.MMDB())
})
r := &Resolver{
main: transform(config.Main),
ipv6: config.IPv6,
cache: cache.New(time.Second * 60),
mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP,
pool: config.Pool,
}
if config.Fallback != nil {
r.fallback = transform(config.Fallback)
}
return r
}

124
dns/server.go Normal file
View File

@ -0,0 +1,124 @@
package dns
import (
"errors"
"fmt"
"net"
"time"
"github.com/Dreamacro/clash/log"
"github.com/miekg/dns"
D "github.com/miekg/dns"
)
var (
address string
server = &Server{}
dnsDefaultTTL uint32 = 600
)
type Server struct {
*D.Server
r *Resolver
}
func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
if s.r.IsFakeIP() {
msg, err := s.handleFakeIP(r)
if err != nil {
D.HandleFailed(w, r)
return
}
msg.SetReply(r)
w.WriteMsg(msg)
return
}
msg, err := s.r.Exchange(r)
if err != nil {
if len(r.Question) > 0 {
q := r.Question[0]
qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String())
log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err)
}
D.HandleFailed(w, r)
return
}
msg.SetReply(r)
w.WriteMsg(msg)
}
func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
if len(r.Question) == 0 {
err = errors.New("should have one question at least")
return
}
q := r.Question[0]
cache, expireTime := s.r.cache.GetWithExpire("fakeip:" + q.String())
if cache != nil {
msg = cache.(*D.Msg).Copy()
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
return
}
var ip net.IP
defer func() {
if msg == nil {
return
}
putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg)
putMsgToCache(s.r.cache, ip.String(), msg)
}()
rr := &D.A{}
rr.Hdr = dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: dnsDefaultTTL}
ip = s.r.pool.Get()
rr.A = ip
msg = r.Copy()
msg.Answer = []D.RR{rr}
return
}
func (s *Server) setReslover(r *Resolver) {
s.r = r
}
func ReCreateServer(addr string, resolver *Resolver) error {
if addr == address {
server.setReslover(resolver)
return nil
}
if server.Server != nil {
server.Shutdown()
}
_, port, err := net.SplitHostPort(addr)
if port == "0" || port == "" || err != nil {
return nil
}
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return err
}
p, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
address = addr
server = &Server{r: resolver}
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
go func() {
server.ActivateAndServe()
}()
return nil
}

109
dns/util.go Normal file
View File

@ -0,0 +1,109 @@
package dns
import (
"encoding/json"
"errors"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/log"
yaml "gopkg.in/yaml.v2"
D "github.com/miekg/dns"
)
var (
// EnhancedModeMapping is a mapping for EnhancedMode enum
EnhancedModeMapping = map[string]EnhancedMode{
NORMAL.String(): NORMAL,
FAKEIP.String(): FAKEIP,
MAPPING.String(): MAPPING,
}
)
const (
NORMAL EnhancedMode = iota
FAKEIP
MAPPING
)
type EnhancedMode int
// UnmarshalYAML unserialize EnhancedMode with yaml
func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := EnhancedModeMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalYAML serialize EnhancedMode with yaml
func (e EnhancedMode) MarshalYAML() ([]byte, error) {
return yaml.Marshal(e.String())
}
// UnmarshalJSON unserialize EnhancedMode with json
func (e *EnhancedMode) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := EnhancedModeMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalJSON serialize EnhancedMode with json
func (e EnhancedMode) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
func (e EnhancedMode) String() string {
switch e {
case NORMAL:
return "normal"
case FAKEIP:
return "fake-ip"
case MAPPING:
return "redir-host"
default:
return "unknown"
}
}
func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) {
var ttl time.Duration
if len(msg.Answer) != 0 {
ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second
} else if len(msg.Ns) != 0 {
ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second
} else if len(msg.Extra) != 0 {
ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second
} else {
log.Debugln("[DNS] response msg error: %#v", msg)
return
}
c.Put(key, msg.Copy(), ttl)
}
func setMsgTTL(msg *D.Msg, ttl uint32) {
for _, answer := range msg.Answer {
answer.Header().Ttl = ttl
}
for _, ns := range msg.Ns {
ns.Header().Ttl = ttl
}
for _, extra := range msg.Extra {
extra.Header().Ttl = ttl
}
}

15
go.mod
View File

@ -1,17 +1,20 @@
module github.com/Dreamacro/clash module github.com/Dreamacro/clash
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 github.com/Dreamacro/go-shadowsocks2 v0.1.3
github.com/eapache/queue v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v3.3.3+incompatible github.com/go-chi/chi v4.0.2+incompatible
github.com/go-chi/cors v1.0.0 github.com/go-chi/cors v1.0.0
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.1.0+incompatible github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.0 github.com/gorilla/websocket v1.4.0
github.com/miekg/dns v1.1.9
github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/geoip2-golang v1.2.1
github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/sirupsen/logrus v1.1.0 github.com/sirupsen/logrus v1.4.1
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/eapache/channels.v1 v1.1.0
gopkg.in/yaml.v2 v2.2.1 gopkg.in/yaml.v2 v2.2.2
) )

44
go.sum
View File

@ -1,43 +1,51 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 h1:ugkI+Yw5ArFnhF8KTbJxyWIyvxMCa8jWyUF+wIAulhM= github.com/Dreamacro/go-shadowsocks2 v0.1.3 h1:1ffY/q4e3o+MnztYgIq1iZiX1BWoWQ6D3AIO1kkb8bc=
github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= github.com/Dreamacro/go-shadowsocks2 v0.1.3/go.mod h1:0x17IhQ+mlY6q/ffKRpzaE7u4aHMxxnitTRSrV5G6TU=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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 v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.0.2+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.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/cors v1.0.0/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.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.9 h1:OIdC9wT96RzuZMf2PfKRhFgsStHUUBZLM/lo1LqiM9E=
github.com/miekg/dns v1.1.9/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
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.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
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 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=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,33 +0,0 @@
package hub
import (
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
)
var (
tunnel = T.Instance()
cfg = config.Instance()
listener = proxy.Instance()
)
type Error struct {
Error string `json:"error"`
}
type Errors struct {
Errors map[string]string `json:"errors"`
}
func formatErrors(errorsMap map[string]error) (bool, Errors) {
errors := make(map[string]string)
hasError := false
for key, err := range errorsMap {
if err != nil {
errors[key] = err.Error()
hasError = true
}
}
return hasError, Errors{Errors: errors}
}

View File

@ -1,96 +0,0 @@
package hub
import (
"fmt"
"net/http"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func configRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getConfigs)
r.Put("/", updateConfigs)
return r
}
type configSchema struct {
Port int `json:"port"`
SocksPort int `json:"socket-port"`
RedirPort int `json:"redir-port"`
AllowLan bool `json:"allow-lan"`
Mode string `json:"mode"`
LogLevel string `json:"log-level"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
general := cfg.General()
render.JSON(w, r, configSchema{
Port: general.Port,
SocksPort: general.SocksPort,
RedirPort: general.RedirPort,
AllowLan: general.AllowLan,
Mode: general.Mode.String(),
LogLevel: general.LogLevel.String(),
})
}
func updateConfigs(w http.ResponseWriter, r *http.Request) {
general := &C.General{}
err := render.DecodeJSON(r.Body, general)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{
Error: "Format error",
})
return
}
// update errors
var modeErr, logLevelErr error
// update mode
if general.Mode != nil {
mode, ok := config.ModeMapping[*general.Mode]
if !ok {
modeErr = fmt.Errorf("Mode error")
} else {
cfg.SetMode(mode)
}
}
// update log-level
if general.LogLevel != nil {
level, ok := C.LogLevelMapping[*general.LogLevel]
if !ok {
logLevelErr = fmt.Errorf("Log Level error")
} else {
cfg.SetLogLevel(level)
}
}
hasError, errors := formatErrors(map[string]error{
"mode": modeErr,
"log-level": logLevelErr,
})
if hasError {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, errors)
return
}
// update proxy
cfg.UpdateProxy(config.ProxyConfig{
AllowLan: general.AllowLan,
Port: general.Port,
SocksPort: general.SocksPort,
RedirPort: general.RedirPort,
})
w.WriteHeader(http.StatusNoContent)
}

104
hub/executor/executor.go Normal file
View File

@ -0,0 +1,104 @@
package executor
import (
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
)
// Parse config with default config path
func Parse() (*config.Config, error) {
return ParseWithPath(C.Path.Config())
}
// ParseWithPath parse config with custom config path
func ParseWithPath(path string) (*config.Config, error) {
return config.Parse(path)
}
// ApplyConfig dispatch configure to all parts
func ApplyConfig(cfg *config.Config, force bool) {
if force {
updateGeneral(cfg.General)
}
updateProxies(cfg.Proxies)
updateRules(cfg.Rules)
updateDNS(cfg.DNS)
updateExperimental(cfg.Experimental)
}
func GetGeneral() *config.General {
ports := P.GetPorts()
return &config.General{
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
AllowLan: P.AllowLan(),
Mode: T.Instance().Mode(),
LogLevel: log.Level(),
}
}
func updateExperimental(c *config.Experimental) {
T.Instance().UpdateExperimental(c.IgnoreResolveFail)
}
func updateDNS(c *config.DNS) {
if c.Enable == false {
T.Instance().SetResolver(nil)
dns.ReCreateServer("", nil)
return
}
r := dns.New(dns.Config{
Main: c.NameServer,
Fallback: c.Fallback,
IPv6: c.IPv6,
EnhancedMode: c.EnhancedMode,
Pool: c.FakeIPRange,
})
T.Instance().SetResolver(r)
if err := dns.ReCreateServer(c.Listen, r); err != nil {
log.Errorln("Start DNS server error: %s", err.Error())
return
}
log.Infoln("DNS server listening at: %s", c.Listen)
}
func updateProxies(proxies map[string]C.Proxy) {
tunnel := T.Instance()
oldProxies := tunnel.Proxies()
// close proxy group goroutine
for _, proxy := range oldProxies {
proxy.Destroy()
}
tunnel.UpdateProxies(proxies)
}
func updateRules(rules []C.Rule) {
T.Instance().UpdateRules(rules)
}
func updateGeneral(general *config.General) {
log.SetLevel(general.LogLevel)
T.Instance().SetMode(general.Mode)
allowLan := general.AllowLan
P.SetAllowLan(allowLan)
if err := P.ReCreateHTTP(general.Port); err != nil {
log.Errorln("Start HTTP server error: %s", err.Error())
}
if err := P.ReCreateSocks(general.SocksPort); err != nil {
log.Errorln("Start SOCKS5 server error: %s", err.Error())
}
if err := P.ReCreateRedir(general.RedirPort); err != nil {
log.Errorln("Start Redir server error: %s", err.Error())
}
}

25
hub/hub.go Normal file
View File

@ -0,0 +1,25 @@
package hub
import (
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/hub/route"
)
// Parse call at the beginning of clash
func Parse() error {
cfg, err := executor.Parse()
if err != nil {
return err
}
if cfg.General.ExternalUI != "" {
route.SetUIPath(cfg.General.ExternalUI)
}
if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.Secret)
}
executor.ApplyConfig(cfg, true)
return nil
}

View File

@ -1,217 +0,0 @@
package hub
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
A "github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func proxyRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getProxies)
r.With(parseProxyName).Get("/{name}", getProxy)
r.With(parseProxyName).Get("/{name}/delay", getProxyDelay)
r.With(parseProxyName).Put("/{name}", updateProxy)
return r
}
// When name is composed of a partial escape string, Golang does not unescape it
func parseProxyName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
if newName, err := url.PathUnescape(name); err == nil {
name = newName
}
ctx := context.WithValue(r.Context(), contextKey("proxy name"), name)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
type SampleProxy struct {
Type string `json:"type"`
}
type Selector struct {
Type string `json:"type"`
Now string `json:"now"`
All []string `json:"all"`
}
type URLTest struct {
Type string `json:"type"`
Now string `json:"now"`
}
type Fallback struct {
Type string `json:"type"`
Now string `json:"now"`
}
func transformProxy(proxy C.Proxy) interface{} {
t := proxy.Type()
switch t {
case C.Selector:
selector := proxy.(*A.Selector)
return Selector{
Type: t.String(),
Now: selector.Now(),
All: selector.All(),
}
case C.URLTest:
return URLTest{
Type: t.String(),
Now: proxy.(*A.URLTest).Now(),
}
case C.Fallback:
return Fallback{
Type: t.String(),
Now: proxy.(*A.Fallback).Now(),
}
default:
return SampleProxy{
Type: proxy.Type().String(),
}
}
}
type GetProxiesResponse struct {
Proxies map[string]interface{} `json:"proxies"`
}
func getProxies(w http.ResponseWriter, r *http.Request) {
rawProxies := cfg.Proxies()
proxies := make(map[string]interface{})
for name, proxy := range rawProxies {
proxies[name] = transformProxy(proxy)
}
render.JSON(w, r, GetProxiesResponse{Proxies: proxies})
}
func getProxy(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[name]
if !exist {
w.WriteHeader(http.StatusNotFound)
render.JSON(w, r, Error{
Error: "Proxy not found",
})
return
}
render.JSON(w, r, transformProxy(proxy))
}
type UpdateProxyRequest struct {
Name string `json:"name"`
}
func updateProxy(w http.ResponseWriter, r *http.Request) {
req := UpdateProxyRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{
Error: "Format error",
})
return
}
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[name]
if !exist {
w.WriteHeader(http.StatusNotFound)
render.JSON(w, r, Error{
Error: "Proxy not found",
})
return
}
selector, ok := proxy.(*A.Selector)
if !ok {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{
Error: "Proxy can't update",
})
return
}
if err := selector.Set(req.Name); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{
Error: fmt.Sprintf("Selector update error: %s", err.Error()),
})
return
}
w.WriteHeader(http.StatusNoContent)
}
type GetProxyDelayRequest struct {
URL string `json:"url"`
Timeout int16 `json:"timeout"`
}
type GetProxyDelayResponse struct {
Delay int16 `json:"delay"`
}
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, Error{
Error: "Format error",
})
return
}
name := r.Context().Value(contextKey("proxy name")).(string)
proxies := cfg.Proxies()
proxy, exist := proxies[name]
if !exist {
w.WriteHeader(http.StatusNotFound)
render.JSON(w, r, Error{
Error: "Proxy not found",
})
return
}
sigCh := make(chan int16)
go func() {
t, err := A.DelayTest(proxy, url)
if err != nil {
sigCh <- 0
}
sigCh <- t
}()
select {
case <-time.After(time.Millisecond * time.Duration(timeout)):
w.WriteHeader(http.StatusRequestTimeout)
render.JSON(w, r, Error{
Error: "Proxy delay test timeout",
})
case t := <-sigCh:
if t == 0 {
w.WriteHeader(http.StatusServiceUnavailable)
render.JSON(w, r, Error{
Error: "An error occurred in the delay test",
})
} else {
render.JSON(w, r, GetProxyDelayResponse{
Delay: t,
})
}
}
}

102
hub/route/configs.go Normal file
View File

@ -0,0 +1,102 @@
package route
import (
"net/http"
"path/filepath"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func configRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getConfigs)
r.Put("/", updateConfigs)
r.Patch("/", patchConfigs)
return r
}
type configSchema struct {
Port *int `json:"port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
AllowLan *bool `json:"allow-lan"`
Mode *T.Mode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
general := executor.GetGeneral()
render.JSON(w, r, general)
}
func pointerOrDefault(p *int, def int) int {
if p != nil {
return *p
}
return def
}
func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
if general.AllowLan != nil {
P.SetAllowLan(*general.AllowLan)
}
ports := P.GetPorts()
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
if general.Mode != nil {
T.Instance().SetMode(*general.Mode)
}
if general.LogLevel != nil {
log.SetLevel(*general.LogLevel)
}
render.NoContent(w, r)
}
type updateConfigRequest struct {
Path string `json:"path"`
}
func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := updateConfigRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absoluted path"))
return
}
force := r.URL.Query().Get("force") == "true"
cfg, err := executor.ParseWithPath(req.Path)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(err.Error()))
return
}
executor.ApplyConfig(cfg, force)
render.NoContent(w, r)
}

12
hub/route/ctxkeys.go Normal file
View File

@ -0,0 +1,12 @@
package route
var (
CtxKeyProxyName = contextKey("proxy name")
CtxKeyProxy = contextKey("proxy")
)
type contextKey string
func (c contextKey) String() string {
return "clash context key " + string(c)
}

22
hub/route/errors.go Normal file
View File

@ -0,0 +1,22 @@
package route
var (
ErrUnauthorized = newError("Unauthorized")
ErrBadRequest = newError("Body invalid")
ErrForbidden = newError("Forbidden")
ErrNotFound = newError("Resource not found")
ErrRequestTimeout = newError("Timeout")
)
// HTTPError is custom HTTP error for API
type HTTPError struct {
Message string `json:"message"`
}
func (e *HTTPError) Error() string {
return e.Message
}
func newError(msg string) *HTTPError {
return &HTTPError{Message: msg}
}

136
hub/route/proxies.go Normal file
View File

@ -0,0 +1,136 @@
package route
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
A "github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant"
T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
)
func proxyRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getProxies)
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProxyName, findProxyByName)
r.Get("/", getProxy)
r.Get("/delay", getProxyDelay)
r.Put("/", updateProxy)
})
return r
}
// When name is composed of a partial escape string, Golang does not unescape it
func parseProxyName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
if newName, err := url.PathUnescape(name); err == nil {
name = newName
}
ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func findProxyByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string)
proxies := T.Instance().Proxies()
proxy, exist := proxies[name]
if !exist {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
return
}
ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := T.Instance().Proxies()
render.JSON(w, r, render.M{
"proxies": proxies,
})
}
func getProxy(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
render.JSON(w, r, proxy)
}
type UpdateProxyRequest struct {
Name string `json:"name"`
}
func updateProxy(w http.ResponseWriter, r *http.Request) {
req := UpdateProxyRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
proxy := r.Context().Value(CtxKeyProxy).(*A.Proxy)
selector, ok := proxy.ProxyAdapter.(*A.Selector)
if !ok {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("Must be a Selector"))
return
}
if err := selector.Set(req.Name); err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
return
}
render.NoContent(w, r)
}
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
sigCh := make(chan uint16)
go func() {
t, err := proxy.URLTest(url)
if err != nil {
sigCh <- 0
}
sigCh <- t
}()
select {
case <-time.After(time.Millisecond * time.Duration(timeout)):
render.Status(r, http.StatusRequestTimeout)
render.JSON(w, r, ErrRequestTimeout)
case t := <-sigCh:
if t == 0 {
render.Status(r, http.StatusServiceUnavailable)
render.JSON(w, r, newError("An error occurred in the delay test"))
} else {
render.JSON(w, r, render.M{
"delay": t,
})
}
}
}

View File

@ -1,8 +1,10 @@
package hub package route
import ( import (
"net/http" "net/http"
T "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"
) )
@ -10,7 +12,6 @@ import (
func ruleRouter() http.Handler { func ruleRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/", getRules) r.Get("/", getRules)
r.Put("/", updateRules)
return r return r
} }
@ -20,14 +21,10 @@ type Rule struct {
Proxy string `json:"proxy"` Proxy string `json:"proxy"`
} }
type GetRulesResponse struct {
Rules []Rule `json:"rules"`
}
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := cfg.Rules() rawRules := T.Instance().Rules()
var rules []Rule rules := []Rule{}
for _, rule := range rawRules { for _, rule := range rawRules {
rules = append(rules, Rule{ rules = append(rules, Rule{
Type: rule.RuleType().String(), Type: rule.RuleType().String(),
@ -36,20 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
}) })
} }
w.WriteHeader(http.StatusOK) render.JSON(w, r, render.M{
render.JSON(w, r, GetRulesResponse{ "rules": rules,
Rules: rules,
}) })
} }
func updateRules(w http.ResponseWriter, r *http.Request) {
err := cfg.UpdateRules()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
render.JSON(w, r, Error{
Error: err.Error(),
})
return
}
w.WriteHeader(http.StatusNoContent)
}

View File

@ -1,4 +1,4 @@
package hub package route
import ( import (
"encoding/json" "encoding/json"
@ -6,65 +6,75 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/log"
C "github.com/Dreamacro/clash/constant"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/cors" "github.com/go-chi/cors"
"github.com/go-chi/render" "github.com/go-chi/render"
log "github.com/sirupsen/logrus"
) )
var secret = "" var (
serverSecret = ""
serverAddr = ""
uiPath = ""
)
type Traffic struct { type Traffic struct {
Up int64 `json:"up"` Up int64 `json:"up"`
Down int64 `json:"down"` Down int64 `json:"down"`
} }
func newHub(signal chan struct{}) { func SetUIPath(path string) {
var addr string uiPath = path
ch := config.Instance().Subscribe() }
signal <- struct{}{}
count := 0 func Start(addr string, secret string) {
for { if serverAddr != "" {
elm := <-ch return
event := elm.(*config.Event)
switch event.Type {
case "external-controller":
addr = event.Payload.(string)
count++
case "secret":
secret = event.Payload.(string)
count++
}
if count == 2 {
break
}
} }
serverAddr = addr
serverSecret = secret
r := chi.NewRouter() r := chi.NewRouter()
cors := cors.New(cors.Options{ cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"}, AllowedHeaders: []string{"Content-Type", "Authorization"},
MaxAge: 300, MaxAge: 300,
}) })
r.Use(cors.Handler, authentication) root := chi.NewRouter().With(jsonContentType)
root.Get("/traffic", traffic)
root.Get("/logs", getLogs)
r.With(jsonContentType).Get("/traffic", traffic) r.Get("/", hello)
r.With(jsonContentType).Get("/logs", getLogs) r.Group(func(r chi.Router) {
r.Mount("/configs", configRouter()) r.Use(cors.Handler, authentication)
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter())
log.Infof("RESTful API listening at: %s", addr) r.Mount("/", root)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter())
})
if uiPath != "" {
r.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
log.Infoln("RESTful API listening at: %s", addr)
err := http.ListenAndServe(addr, r) err := http.ListenAndServe(addr, r)
if err != nil { if err != nil {
log.Errorf("External controller error: %s", err.Error()) log.Errorln("External controller error: %s", err.Error())
} }
} }
@ -81,18 +91,16 @@ 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)
if secret == "" { if serverSecret == "" {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
hasUnvalidHeader := text[0] != "Bearer" hasUnvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != secret hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret { if hasUnvalidHeader || hasUnvalidSecret {
w.WriteHeader(http.StatusUnauthorized) render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, Error{ render.JSON(w, r, ErrUnauthorized)
Error: "Authentication failed",
})
return return
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -100,17 +108,15 @@ func authentication(next http.Handler) http.Handler {
return http.HandlerFunc(fn) return http.HandlerFunc(fn)
} }
type contextKey string func hello(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"hello": "clash"})
func (c contextKey) String() string {
return "clash context key " + string(c)
} }
func traffic(w http.ResponseWriter, r *http.Request) { func traffic(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) render.Status(r, http.StatusOK)
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
t := tunnel.Traffic() t := T.Instance().Traffic()
for range tick.C { for range tick.C {
up, down := t.Now() up, down := t.Now()
if err := json.NewEncoder(w).Encode(Traffic{ if err := json.NewEncoder(w).Encode(Traffic{
@ -134,29 +140,18 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
levelText = "info" levelText = "info"
} }
level, ok := C.LogLevelMapping[levelText] level, ok := log.LogLevelMapping[levelText]
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, Error{ render.JSON(w, r, ErrBadRequest)
Error: "Level error",
})
return return
} }
src := tunnel.Log() sub := log.Subscribe()
sub, err := src.Subscribe()
defer src.UnSubscribe(sub)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
render.JSON(w, r, Error{
Error: err.Error(),
})
return
}
render.Status(r, http.StatusOK) render.Status(r, http.StatusOK)
for elm := range sub { for elm := range sub {
log := elm.(T.Log) log := elm.(*log.Event)
if log.LogLevel > level { if log.LogLevel < level {
continue continue
} }
@ -169,10 +164,3 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} }
} }
// Run initial hub
func Run() {
signal := make(chan struct{})
go newHub(signal)
<-signal
}

73
log/level.go Normal file
View File

@ -0,0 +1,73 @@
package log
import (
"encoding/json"
"errors"
)
var (
// LogLevelMapping is a mapping for LogLevel enum
LogLevelMapping = map[string]LogLevel{
ERROR.String(): ERROR,
WARNING.String(): WARNING,
INFO.String(): INFO,
DEBUG.String(): DEBUG,
SILENT.String(): SILENT,
}
)
const (
DEBUG LogLevel = iota
INFO
WARNING
ERROR
SILENT
)
type LogLevel int
// UnmarshalYAML unserialize LogLevel with yaml
func (l *LogLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
unmarshal(&tp)
level, exist := LogLevelMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*l = level
return nil
}
// UnmarshalJSON unserialize LogLevel with json
func (l *LogLevel) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
level, exist := LogLevelMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*l = level
return nil
}
// MarshalJSON serialize LogLevel with json
func (l LogLevel) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
func (l LogLevel) String() string {
switch l {
case INFO:
return "info"
case WARNING:
return "warning"
case ERROR:
return "error"
case DEBUG:
return "debug"
case SILENT:
return "silent"
default:
return "unknown"
}
}

93
log/log.go Normal file
View File

@ -0,0 +1,93 @@
package log
import (
"fmt"
"github.com/Dreamacro/clash/common/observable"
log "github.com/sirupsen/logrus"
)
var (
logCh = make(chan interface{})
source = observable.NewObservable(logCh)
level = INFO
)
func init() {
log.SetLevel(log.DebugLevel)
}
type Event struct {
LogLevel LogLevel
Payload string
}
func (e *Event) Type() string {
return e.LogLevel.String()
}
func Infoln(format string, v ...interface{}) {
event := newLog(INFO, format, v...)
logCh <- event
print(event)
}
func Warnln(format string, v ...interface{}) {
event := newLog(WARNING, format, v...)
logCh <- event
print(event)
}
func Errorln(format string, v ...interface{}) {
event := newLog(ERROR, format, v...)
logCh <- event
print(event)
}
func Debugln(format string, v ...interface{}) {
event := newLog(DEBUG, format, v...)
logCh <- event
print(event)
}
func Fatalln(format string, v ...interface{}) {
log.Fatalf(format, v...)
}
func Subscribe() observable.Subscription {
sub, _ := source.Subscribe()
return sub
}
func Level() LogLevel {
return level
}
func SetLevel(newLevel LogLevel) {
level = newLevel
}
func print(data *Event) {
if data.LogLevel < level {
return
}
switch data.LogLevel {
case INFO:
log.Infoln(data.Payload)
case WARNING:
log.Warnln(data.Payload)
case ERROR:
log.Errorln(data.Payload)
case DEBUG:
log.Debugln(data.Payload)
}
}
func newLog(logLevel LogLevel, format string, v ...interface{}) *Event {
return &Event{
LogLevel: logLevel,
Payload: fmt.Sprintf(format, v...),
}
}

36
main.go
View File

@ -1,46 +1,54 @@
package main package main
import ( import (
"flag"
"fmt"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"runtime"
"syscall" "syscall"
"flag"
"path"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub"
"github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var ( var (
version bool
homedir string homedir string
) )
func init() { func init() {
flag.StringVar(&homedir, "d", "", "set configuration directory") flag.StringVar(&homedir, "d", "", "set configuration directory")
flag.BoolVar(&version, "v", false, "show current version of clash")
flag.Parse() flag.Parse()
} }
func main() { func main() {
tunnel.Instance().Run() if version {
proxy.Instance().Run() fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime)
hub.Run() return
}
if (homedir != "") { // enable tls 1.3 and remove when go 1.13
if !path.IsAbs(homedir) { os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
if homedir != "" {
if !filepath.IsAbs(homedir) {
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
homedir = path.Join(currentDir, homedir) homedir = filepath.Join(currentDir, homedir)
} }
C.SetHomeDir(homedir) C.SetHomeDir(homedir)
} }
config.Init() if err := config.Init(C.Path.HomeDir()); err != nil {
err := config.Instance().Parse() log.Fatalf("Initial configuration directory error: %s", err.Error())
if err != nil { }
if err := hub.Parse(); err != nil {
log.Fatalf("Parse config error: %s", err.Error()) log.Fatalf("Parse config error: %s", err.Error())
} }

View File

@ -5,36 +5,34 @@ import (
"net" "net"
"net/http" "net/http"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus"
) )
var ( var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
func NewHttpProxy(addr string) (*C.ProxySignal, error) { type HttpListener struct {
net.Listener
address string
closed bool
}
func NewHttpProxy(addr string) (*HttpListener, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
hl := &HttpListener{l, addr, false}
done := make(chan struct{})
closed := make(chan struct{})
signal := &C.ProxySignal{
Done: done,
Closed: closed,
}
go func() { go func() {
log.Infof("HTTP proxy listening at: %s", addr) log.Infoln("HTTP proxy listening at: %s", addr)
for { for {
c, err := l.Accept() c, err := hl.Accept()
if err != nil { if err != nil {
if _, open := <-done; !open { if hl.closed {
break break
} }
continue continue
@ -43,20 +41,22 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) {
} }
}() }()
go func() { return hl, nil
<-done }
close(done)
l.Close()
closed <- struct{}{}
}()
return signal, nil func (l *HttpListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *HttpListener) Address() string {
return l.address
} }
func handleConn(conn net.Conn) { func handleConn(conn net.Conn) {
br := bufio.NewReader(conn) br := bufio.NewReader(conn)
request, err := http.ReadRequest(br) request, err := http.ReadRequest(br)
if err != nil { if err != nil || request.URL.Host == "" {
conn.Close() conn.Close()
return return
} }

View File

@ -1,116 +1,150 @@
package proxy package proxy
import ( import (
"sync" "fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"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"
) )
var ( var (
listener *Listener allowLan = false
once sync.Once
socksListener *socks.SockListener
httpListener *http.HttpListener
redirListener *redir.RedirListener
) )
type Listener struct { type listener interface {
// signal for update Close()
httpSignal *C.ProxySignal Address() string
socksSignal *C.ProxySignal
redirSignal *C.ProxySignal
} }
func (l *Listener) updateHTTP(addr string) error { type Ports struct {
if l.httpSignal != nil { Port int `json:"port"`
signal := l.httpSignal SocksPort int `json:"socks-port"`
signal.Done <- struct{}{} RedirPort int `json:"redir-port"`
<-signal.Closed
l.httpSignal = nil
}
signal, err := http.NewHttpProxy(addr)
if err != nil {
return err
}
l.httpSignal = signal
return nil
} }
func (l *Listener) updateSocks(addr string) error { func AllowLan() bool {
if l.socksSignal != nil { return allowLan
signal := l.socksSignal
signal.Done <- struct{}{}
<-signal.Closed
l.socksSignal = nil
}
signal, err := socks.NewSocksProxy(addr)
if err != nil {
return err
}
l.socksSignal = signal
return nil
} }
func (l *Listener) updateRedir(addr string) error { func SetAllowLan(al bool) {
if l.redirSignal != nil { allowLan = al
signal := l.redirSignal
signal.Done <- struct{}{}
<-signal.Closed
l.redirSignal = nil
}
signal, err := redir.NewRedirProxy(addr)
if err != nil {
return err
}
l.redirSignal = signal
return nil
} }
func (l *Listener) process(signal chan<- struct{}) { func ReCreateHTTP(port int) error {
sub := config.Instance().Subscribe() addr := genAddr(port, allowLan)
signal <- struct{}{}
reportCH := config.Instance().Report() if httpListener != nil {
for elm := range sub { if httpListener.Address() == addr {
event := elm.(*config.Event) return nil
switch event.Type {
case "http-addr":
addr := event.Payload.(string)
err := l.updateHTTP(addr)
reportCH <- &config.Event{Type: "http-addr", Payload: err == nil}
case "socks-addr":
addr := event.Payload.(string)
err := l.updateSocks(addr)
reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil}
case "redir-addr":
addr := event.Payload.(string)
err := l.updateRedir(addr)
reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil}
} }
httpListener.Close()
httpListener = nil
} }
if portIsZero(addr) {
return nil
}
var err error
httpListener, err = http.NewHttpProxy(addr)
if err != nil {
return err
}
return nil
} }
// Run ensure config monitoring func ReCreateSocks(port int) error {
func (l *Listener) Run() { addr := genAddr(port, allowLan)
signal := make(chan struct{})
go l.process(signal) if socksListener != nil {
<-signal if socksListener.Address() == addr {
return nil
}
socksListener.Close()
socksListener = nil
}
if portIsZero(addr) {
return nil
}
var err error
socksListener, err = socks.NewSocksProxy(addr)
if err != nil {
return err
}
return nil
} }
func newListener() *Listener { func ReCreateRedir(port int) error {
return &Listener{} addr := genAddr(port, allowLan)
if redirListener != nil {
if redirListener.Address() == addr {
return nil
}
redirListener.Close()
redirListener = nil
}
if portIsZero(addr) {
return nil
}
var err error
redirListener, err = redir.NewRedirProxy(addr)
if err != nil {
return err
}
return nil
} }
// Instance return singleton instance of Listener // GetPorts return the ports of proxy servers
func Instance() *Listener { func GetPorts() *Ports {
once.Do(func() { ports := &Ports{}
listener = newListener()
}) if httpListener != nil {
return listener _, portStr, _ := net.SplitHostPort(httpListener.Address())
port, _ := strconv.Atoi(portStr)
ports.Port = port
}
if socksListener != nil {
_, portStr, _ := net.SplitHostPort(socksListener.Address())
port, _ := strconv.Atoi(portStr)
ports.SocksPort = port
}
if redirListener != nil {
_, portStr, _ := net.SplitHostPort(redirListener.Address())
port, _ := strconv.Atoi(portStr)
ports.RedirPort = port
}
return ports
}
func portIsZero(addr string) bool {
_, port, err := net.SplitHostPort(addr)
if port == "0" || port == "" || err != nil {
return true
}
return false
}
func genAddr(port int, allowLan bool) string {
if allowLan {
return fmt.Sprintf(":%d", port)
}
return fmt.Sprintf("127.0.0.1:%d", port)
} }

View File

@ -5,34 +5,33 @@ import (
"github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus"
) )
var ( var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
func NewRedirProxy(addr string) (*C.ProxySignal, error) { type RedirListener struct {
net.Listener
address string
closed bool
}
func NewRedirProxy(addr string) (*RedirListener, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rl := &RedirListener{l, addr, false}
done := make(chan struct{})
closed := make(chan struct{})
signal := &C.ProxySignal{
Done: done,
Closed: closed,
}
go func() { go func() {
log.Infof("Redir proxy listening at: %s", addr) log.Infoln("Redir proxy listening at: %s", addr)
for { for {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
if _, open := <-done; !open { if rl.closed {
break break
} }
continue continue
@ -41,14 +40,16 @@ func NewRedirProxy(addr string) (*C.ProxySignal, error) {
} }
}() }()
go func() { return rl, nil
<-done }
close(done)
l.Close()
closed <- struct{}{}
}()
return signal, nil func (l *RedirListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *RedirListener) Address() string {
return l.address
} }
func handleRedir(conn net.Conn) { func handleRedir(conn net.Conn) {
@ -58,5 +59,5 @@ func handleRedir(conn net.Conn) {
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) conn.(*net.TCPConn).SetKeepAlive(true)
tun.Add(adapters.NewSocket(target, conn)) tun.Add(adapters.NewSocket(target, conn, C.REDIR, C.TCP))
} }

View File

@ -5,10 +5,10 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/clash/component/socks5"
) )
func parserPacket(c net.Conn) (socks.Addr, error) { func parserPacket(c net.Conn) (socks5.Addr, error) {
const ( const (
PfInout = 0 PfInout = 0
PfIn = 1 PfIn = 1
@ -51,7 +51,7 @@ func parserPacket(c net.Conn) (socks.Addr, error) {
} }
addr := make([]byte, 1+net.IPv4len+2) addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks.AtypIPv4 addr[0] = socks5.AtypIPv4
copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) copy(addr[1:1+net.IPv4len], nl.rdaddr[:4])
copy(addr[1+net.IPv4len:], nl.rdxport[:2]) copy(addr[1+net.IPv4len:], nl.rdxport[:2])
return addr, nil return addr, nil

View File

@ -0,0 +1,52 @@
package redir
import (
"errors"
"net"
"syscall"
"unsafe"
"github.com/Dreamacro/clash/component/socks5"
)
const (
SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
)
func parserPacket(conn net.Conn) (socks5.Addr, error) {
c, ok := conn.(*net.TCPConn)
if !ok {
return nil, errors.New("only work with TCP connection")
}
rc, err := c.SyscallConn()
if err != nil {
return nil, err
}
var addr socks5.Addr
rc.Control(func(fd uintptr) {
addr, err = getorigdst(fd)
})
return addr, err
}
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
func getorigdst(fd uintptr) (socks5.Addr, error) {
raw := syscall.RawSockaddrInet4{}
siz := unsafe.Sizeof(raw)
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0)
if err != 0 {
return nil, err
}
addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks5.AtypIPv4
copy(addr[1:1+net.IPv4len], raw.Addr[:])
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
return addr, nil
}

View File

@ -6,7 +6,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/clash/component/socks5"
) )
const ( const (
@ -14,7 +14,7 @@ const (
IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
) )
func parserPacket(conn net.Conn) (socks.Addr, error) { func parserPacket(conn net.Conn) (socks5.Addr, error) {
c, ok := conn.(*net.TCPConn) c, ok := conn.(*net.TCPConn)
if !ok { if !ok {
return nil, errors.New("only work with TCP connection") return nil, errors.New("only work with TCP connection")
@ -25,7 +25,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) {
return nil, err return nil, err
} }
var addr socks.Addr var addr socks5.Addr
rc.Control(func(fd uintptr) { rc.Control(func(fd uintptr) {
addr, err = getorigdst(fd) addr, err = getorigdst(fd)
@ -35,7 +35,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) {
} }
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
func getorigdst(fd uintptr) (socks.Addr, error) { func getorigdst(fd uintptr) (socks5.Addr, error) {
raw := syscall.RawSockaddrInet4{} raw := syscall.RawSockaddrInet4{}
siz := unsafe.Sizeof(raw) siz := unsafe.Sizeof(raw)
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
@ -43,7 +43,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) {
} }
addr := make([]byte, 1+net.IPv4len+2) addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks.AtypIPv4 addr[0] = socks5.AtypIPv4
copy(addr[1:1+net.IPv4len], raw.Addr[:]) copy(addr[1:1+net.IPv4len], raw.Addr[:])
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]

View File

@ -4,9 +4,9 @@ import (
"errors" "errors"
"net" "net"
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/clash/component/socks5"
) )
func parserPacket(conn net.Conn) (socks.Addr, error) { func parserPacket(conn net.Conn) (socks5.Addr, error) {
return nil, errors.New("Windows not support yet") return nil, errors.New("Windows not support yet")
} }

View File

@ -3,37 +3,36 @@ package socks
import ( import (
"net" "net"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/go-shadowsocks2/socks"
log "github.com/sirupsen/logrus"
) )
var ( var (
tun = tunnel.Instance() tun = tunnel.Instance()
) )
func NewSocksProxy(addr string) (*C.ProxySignal, error) { type SockListener struct {
net.Listener
address string
closed bool
}
func NewSocksProxy(addr string) (*SockListener, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
done := make(chan struct{}) sl := &SockListener{l, addr, false}
closed := make(chan struct{})
signal := &C.ProxySignal{
Done: done,
Closed: closed,
}
go func() { go func() {
log.Infof("SOCKS proxy listening at: %s", addr) log.Infoln("SOCKS proxy listening at: %s", addr)
for { for {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
if _, open := <-done; !open { if sl.closed {
break break
} }
continue continue
@ -42,22 +41,28 @@ func NewSocksProxy(addr string) (*C.ProxySignal, error) {
} }
}() }()
go func() { return sl, nil
<-done }
close(done)
l.Close()
closed <- struct{}{}
}()
return signal, nil func (l *SockListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *SockListener) Address() string {
return l.address
} }
func handleSocks(conn net.Conn) { func handleSocks(conn net.Conn) {
target, err := socks.Handshake(conn) target, command, err := socks5.ServerHandshake(conn)
if err != nil { if err != nil {
conn.Close() conn.Close()
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) conn.(*net.TCPConn).SetKeepAlive(true)
tun.Add(adapters.NewSocket(target, conn)) if command == socks5.CmdUDPAssociate {
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP))
return
}
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
} }

View File

@ -1 +0,0 @@
package socks

View File

@ -1,6 +1,8 @@
package rules package rules
import ( import (
"strings"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -30,7 +32,7 @@ func (d *Domain) Payload() string {
func NewDomain(domain string, adapter string) *Domain { func NewDomain(domain string, adapter string) *Domain {
return &Domain{ return &Domain{
domain: domain, domain: strings.ToLower(domain),
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -33,7 +33,7 @@ func (dk *DomainKeyword) Payload() string {
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
return &DomainKeyword{ return &DomainKeyword{
keyword: keyword, keyword: strings.ToLower(keyword),
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -33,7 +33,7 @@ func (ds *DomainSuffix) Payload() string {
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
return &DomainSuffix{ return &DomainSuffix{
suffix: suffix, suffix: strings.ToLower(suffix),
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -4,28 +4,28 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Final struct { type Match struct {
adapter string adapter string
} }
func (f *Final) RuleType() C.RuleType { func (f *Match) RuleType() C.RuleType {
return C.FINAL return C.MATCH
} }
func (f *Final) IsMatch(metadata *C.Metadata) bool { func (f *Match) IsMatch(metadata *C.Metadata) bool {
return true return true
} }
func (f *Final) Adapter() string { func (f *Match) Adapter() string {
return f.adapter return f.adapter
} }
func (f *Final) Payload() string { func (f *Match) Payload() string {
return "" return ""
} }
func NewFinal(adapter string) *Final { func NewMatch(adapter string) *Match {
return &Final{ return &Match{
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -24,10 +24,10 @@ func (g *GEOIP) RuleType() C.RuleType {
} }
func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { func (g *GEOIP) IsMatch(metadata *C.Metadata) bool {
if metadata.IP == nil { if metadata.DstIP == nil {
return false return false
} }
record, _ := mmdb.Country(*metadata.IP) record, _ := mmdb.Country(*metadata.DstIP)
return record.Country.IsoCode == g.country return record.Country.IsoCode == g.country
} }

View File

@ -7,20 +7,24 @@ import (
) )
type IPCIDR struct { type IPCIDR struct {
ipnet *net.IPNet ipnet *net.IPNet
adapter string adapter string
isSourceIP bool
} }
func (i *IPCIDR) RuleType() C.RuleType { func (i *IPCIDR) RuleType() C.RuleType {
if i.isSourceIP {
return C.SrcIPCIDR
}
return C.IPCIDR return C.IPCIDR
} }
func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool {
if metadata.IP == nil { ip := metadata.DstIP
return false if i.isSourceIP {
ip = metadata.SrcIP
} }
return ip != nil && i.ipnet.Contains(*ip)
return i.ipnet.Contains(*metadata.IP)
} }
func (i *IPCIDR) Adapter() string { func (i *IPCIDR) Adapter() string {
@ -31,12 +35,14 @@ func (i *IPCIDR) Payload() string {
return i.ipnet.String() return i.ipnet.String()
} }
func NewIPCIDR(s string, adapter string) *IPCIDR { func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR {
_, ipnet, err := net.ParseCIDR(s) _, ipnet, err := net.ParseCIDR(s)
if err != nil { if err != nil {
return nil
} }
return &IPCIDR{ return &IPCIDR{
ipnet: ipnet, ipnet: ipnet,
adapter: adapter, adapter: adapter,
isSourceIP: isSourceIP,
} }
} }

47
rules/port.go Normal file
View File

@ -0,0 +1,47 @@
package rules
import (
"strconv"
C "github.com/Dreamacro/clash/constant"
)
type Port struct {
adapter string
port string
isSource bool
}
func (p *Port) RuleType() C.RuleType {
if p.isSource {
return C.SrcPort
}
return C.DstPort
}
func (p *Port) IsMatch(metadata *C.Metadata) bool {
if p.isSource {
return metadata.SrcPort == p.port
}
return metadata.DstPort == p.port
}
func (p *Port) Adapter() string {
return p.adapter
}
func (p *Port) Payload() string {
return p.port
}
func NewPort(port string, adapter string, isSource bool) *Port {
_, err := strconv.Atoi(port)
if err != nil {
return nil
}
return &Port{
adapter: adapter,
port: port,
isSource: isSource,
}
}

View File

@ -5,28 +5,21 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"sync" "strings"
"time" "time"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/common/pool"
) )
const ( func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
// io.Copy default buffer size is 32 KiB conn := newTrafficTrack(outbound, t.traffic)
// 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
bufferSize = 20 * 1024
)
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
req := request.R req := request.R
host := req.Host host := req.Host
for { for {
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
req.Header.Set("Connection", "close") req.Header.Set("Connection", "close")
req.RequestURI = "" req.RequestURI = ""
adapters.RemoveHopByHopHeaders(req.Header) adapters.RemoveHopByHopHeaders(req.Header)
@ -48,12 +41,16 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
} else { } else {
resp.Close = true resp.Close = true
} }
err = resp.Write(request.Conn()) err = resp.Write(request)
if err != nil || resp.Close { if err != nil || resp.Close {
break break
} }
req, err = http.ReadRequest(bufio.NewReader(request.Conn())) if !keepAlive {
break
}
req, err = http.ReadRequest(bufio.NewReader(request))
if err != nil { if err != nil {
break break
} }
@ -66,9 +63,52 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
} }
} }
func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
conn := newTrafficTrack(proxy.Conn(), t.traffic) conn := newTrafficTrack(outbound, t.traffic)
relay(request.Conn(), conn) relay(request, conn)
}
func (t *Tunnel) handleUDPOverTCP(conn net.Conn, pc net.PacketConn, addr net.Addr) error {
ch := make(chan error, 1)
go func() {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf)
for {
n, err := conn.Read(buf)
if err != nil {
ch <- err
return
}
pc.SetReadDeadline(time.Now().Add(120 * time.Second))
if _, err = pc.WriteTo(buf[:n], addr); err != nil {
ch <- err
return
}
t.traffic.Up() <- int64(n)
ch <- nil
}
}()
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf)
for {
pc.SetReadDeadline(time.Now().Add(120 * time.Second))
n, _, err := pc.ReadFrom(buf)
if err != nil {
break
}
if _, err := conn.Write(buf[:n]); err != nil {
break
}
t.traffic.Down() <- int64(n)
}
<-ch
return nil
} }
// relay copies between left and right bidirectionally. // relay copies between left and right bidirectionally.
@ -76,16 +116,16 @@ func relay(leftConn, rightConn net.Conn) {
ch := make(chan error) ch := make(chan error)
go func() { go func() {
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
_, err := io.CopyBuffer(leftConn, rightConn, buf) _, err := io.CopyBuffer(leftConn, rightConn, buf)
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
leftConn.SetReadDeadline(time.Now()) leftConn.SetReadDeadline(time.Now())
ch <- err ch <- err
}() }()
buf := bufPool.Get().([]byte) buf := pool.BufPool.Get().([]byte)
io.CopyBuffer(rightConn, leftConn, buf) io.CopyBuffer(rightConn, leftConn, buf)
bufPool.Put(buf[:cap(buf)]) pool.BufPool.Put(buf[:cap(buf)])
rightConn.SetReadDeadline(time.Now()) rightConn.SetReadDeadline(time.Now())
<-ch <-ch
} }

View File

@ -1,51 +0,0 @@
package tunnel
import (
"fmt"
C "github.com/Dreamacro/clash/constant"
log "github.com/sirupsen/logrus"
)
type Log struct {
LogLevel C.LogLevel
Payload string
}
func (l *Log) Type() string {
return l.LogLevel.String()
}
func print(data Log) {
switch data.LogLevel {
case C.INFO:
log.Infoln(data.Payload)
case C.WARNING:
log.Warnln(data.Payload)
case C.ERROR:
log.Errorln(data.Payload)
case C.DEBUG:
log.Debugln(data.Payload)
}
}
func (t *Tunnel) subscribeLogs() {
sub, err := t.observable.Subscribe()
if err != nil {
log.Fatalf("Can't subscribe tunnel log: %s", err.Error())
}
for elm := range sub {
data := elm.(Log)
if data.LogLevel <= t.logLevel {
print(data)
}
}
}
func newLog(logLevel C.LogLevel, format string, v ...interface{}) Log {
return Log{
LogLevel: logLevel,
Payload: fmt.Sprintf(format, v...),
}
}

65
tunnel/mode.go Normal file
View File

@ -0,0 +1,65 @@
package tunnel
import (
"encoding/json"
"errors"
)
type Mode int
var (
// ModeMapping is a mapping for Mode enum
ModeMapping = map[string]Mode{
Global.String(): Global,
Rule.String(): Rule,
Direct.String(): Direct,
}
)
const (
Global Mode = iota
Rule
Direct
)
// UnmarshalJSON unserialize Mode
func (m *Mode) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := ModeMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*m = mode
return nil
}
// UnmarshalYAML unserialize Mode with yaml
func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
unmarshal(&tp)
mode, exist := ModeMapping[tp]
if !exist {
return errors.New("invalid mode")
}
*m = mode
return nil
}
// MarshalJSON serialize Mode
func (m Mode) MarshalJSON() ([]byte, error) {
return json.Marshal(m.String())
}
func (m Mode) String() string {
switch m {
case Global:
return "Global"
case Rule:
return "Rule"
case Direct:
return "Direct"
default:
return "Unknow"
}
}

View File

@ -1,15 +1,17 @@
package tunnel package tunnel
import ( import (
"fmt"
"net"
"sync" "sync"
"time" "time"
InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" InboundAdapter "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/observable"
cfg "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/log"
"gopkg.in/eapache/channels.v1" channels "gopkg.in/eapache/channels.v1"
) )
var ( var (
@ -17,21 +19,20 @@ var (
once sync.Once once sync.Once
) )
// Tunnel handle proxy socket and HTTP/SOCKS socket // Tunnel handle relay inbound proxy and outbound proxy
type Tunnel struct { type Tunnel struct {
queue *channels.InfiniteChannel queue *channels.InfiniteChannel
rules []C.Rule rules []C.Rule
proxies map[string]C.Proxy proxies map[string]C.Proxy
configLock *sync.RWMutex configMux *sync.RWMutex
traffic *C.Traffic traffic *C.Traffic
resolver *dns.Resolver
// experimental features
ignoreResolveFail bool
// Outbound Rule // Outbound Rule
mode cfg.Mode mode Mode
// Log
logCh chan interface{}
observable *observable.Observable
logLevel C.LogLevel
} }
// Add request to queue // Add request to queue
@ -44,33 +45,54 @@ func (t *Tunnel) Traffic() *C.Traffic {
return t.traffic return t.traffic
} }
// Log return clash log stream // Rules return all rules
func (t *Tunnel) Log() *observable.Observable { func (t *Tunnel) Rules() []C.Rule {
return t.observable return t.rules
} }
func (t *Tunnel) configMonitor(signal chan<- struct{}) { // UpdateRules handle update rules
sub := cfg.Instance().Subscribe() func (t *Tunnel) UpdateRules(rules []C.Rule) {
signal <- struct{}{} t.configMux.Lock()
for elm := range sub { t.rules = rules
event := elm.(*cfg.Event) t.configMux.Unlock()
switch event.Type { }
case "proxies":
proxies := event.Payload.(map[string]C.Proxy) // Proxies return all proxies
t.configLock.Lock() func (t *Tunnel) Proxies() map[string]C.Proxy {
t.proxies = proxies return t.proxies
t.configLock.Unlock() }
case "rules":
rules := event.Payload.([]C.Rule) // UpdateProxies handle update proxies
t.configLock.Lock() func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy) {
t.rules = rules t.configMux.Lock()
t.configLock.Unlock() t.proxies = proxies
case "mode": t.configMux.Unlock()
t.mode = event.Payload.(cfg.Mode) }
case "log-level":
t.logLevel = event.Payload.(C.LogLevel) // UpdateExperimental handle update experimental config
} func (t *Tunnel) UpdateExperimental(ignoreResolveFail bool) {
} t.configMux.Lock()
t.ignoreResolveFail = ignoreResolveFail
t.configMux.Unlock()
}
// Mode return current mode
func (t *Tunnel) Mode() Mode {
return t.mode
}
// SetMode change the mode of tunnel
func (t *Tunnel) SetMode(mode Mode) {
t.mode = mode
}
// SetResolver change the resolver of tunnel
func (t *Tunnel) SetResolver(resolver *dns.Resolver) {
t.resolver = resolver
}
func (t *Tunnel) hasResolver() bool {
return t.resolver != nil
} }
func (t *Tunnel) process() { func (t *Tunnel) process() {
@ -82,23 +104,73 @@ func (t *Tunnel) process() {
} }
} }
func (t *Tunnel) resolveIP(host string) (net.IP, error) {
if t.resolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
return ipAddr.IP, nil
}
return t.resolver.ResolveIP(host)
}
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil
}
func (t *Tunnel) handleConn(localConn C.ServerAdapter) { func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
defer localConn.Close() defer localConn.Close()
metadata := localConn.Metadata() metadata := localConn.Metadata()
if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
// preprocess enhanced-mode metadata
if t.needLookupIP(metadata) {
host, exist := t.resolver.IPToHost(*metadata.DstIP)
if exist {
metadata.Host = host
metadata.AddrType = C.AtypDomainName
if t.resolver.IsFakeIP() {
metadata.DstIP = nil
}
}
}
var proxy C.Proxy var proxy C.Proxy
switch t.mode { switch t.mode {
case cfg.Direct: case Direct:
proxy = t.proxies["DIRECT"] proxy = t.proxies["DIRECT"]
case cfg.Global: case Global:
proxy = t.proxies["GLOBAL"] proxy = t.proxies["GLOBAL"]
// Rule // Rule
default: default:
proxy = t.match(metadata) var err error
proxy, err = t.match(metadata)
if err != nil {
return
}
} }
remoConn, err := proxy.Generator(metadata)
if metadata.NetWork == C.UDP {
pc, addr, err := proxy.DialUDP(metadata)
defer pc.Close()
if err != nil {
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error())
}
t.handleUDPOverTCP(localConn, pc, addr)
return
}
remoConn, err := proxy.Dial(metadata)
if err != nil { if err != nil {
t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error()) log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error())
return return
} }
defer remoConn.Close() defer remoConn.Close()
@ -107,48 +179,59 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
case *InboundAdapter.HTTPAdapter: case *InboundAdapter.HTTPAdapter:
t.handleHTTP(adapter, remoConn) t.handleHTTP(adapter, remoConn)
case *InboundAdapter.SocketAdapter: case *InboundAdapter.SocketAdapter:
t.handleSOCKS(adapter, remoConn) t.handleSocket(adapter, remoConn)
} }
} }
func (t *Tunnel) match(metadata *C.Metadata) C.Proxy { func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
t.configLock.RLock() return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.DstIP == nil
defer t.configLock.RUnlock() }
func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) {
t.configMux.RLock()
defer t.configMux.RUnlock()
var resolved bool
for _, rule := range t.rules { for _, rule := range t.rules {
if !resolved && t.shouldResolveIP(rule, metadata) {
ip, err := t.resolveIP(metadata.Host)
if err != nil {
if !t.ignoreResolveFail {
return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error())
}
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
} else {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.DstIP = &ip
}
resolved = true
}
if rule.IsMatch(metadata) { if rule.IsMatch(metadata) {
a, ok := t.proxies[rule.Adapter()] adapter, ok := t.proxies[rule.Adapter()]
if !ok { if !ok {
continue continue
} }
t.logCh <- newLog(C.INFO, "%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter())
return a if metadata.NetWork == C.UDP && !adapter.SupportUDP() {
continue
}
log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter())
return adapter, nil
} }
} }
t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", metadata.String()) log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
return t.proxies["DIRECT"] return t.proxies["DIRECT"], nil
}
// Run initial task
func (t *Tunnel) Run() {
go t.process()
go t.subscribeLogs()
signal := make(chan struct{})
go t.configMonitor(signal)
<-signal
} }
func newTunnel() *Tunnel { func newTunnel() *Tunnel {
logCh := make(chan interface{})
return &Tunnel{ return &Tunnel{
queue: channels.NewInfiniteChannel(), queue: channels.NewInfiniteChannel(),
proxies: make(map[string]C.Proxy), proxies: make(map[string]C.Proxy),
observable: observable.NewObservable(logCh), configMux: &sync.RWMutex{},
logCh: logCh, traffic: C.NewTraffic(time.Second),
configLock: &sync.RWMutex{}, mode: Rule,
traffic: C.NewTraffic(time.Second),
mode: cfg.Rule,
logLevel: C.INFO,
} }
} }
@ -156,6 +239,7 @@ func newTunnel() *Tunnel {
func Instance() *Tunnel { func Instance() *Tunnel {
once.Do(func() { once.Do(func() {
tunnel = newTunnel() tunnel = newTunnel()
go tunnel.process()
}) })
return tunnel return tunnel
} }