Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
1235c9a939 | |||
82343c70e9 | |||
94d1972782 | |||
ce07eda428 | |||
0fd2f8e5ee | |||
e863c66ad9 | |||
b68af43389 | |||
381f764507 | |||
64e3791654 | |||
40a94be208 | |||
a0f077b091 | |||
7b80377849 | |||
8fff0968bf | |||
ea9cd41197 | |||
5c7fa6b18b | |||
16c9445459 | |||
04e05c6a85 | |||
85a67988b9 | |||
2fd59cb31c | |||
220e4f0608 | |||
3e68faecb2 | |||
eb778ad6e2 |
@ -2,14 +2,13 @@ language: go
|
|||||||
sudo: false
|
sudo: false
|
||||||
go:
|
go:
|
||||||
- "1.11"
|
- "1.11"
|
||||||
before_install:
|
|
||||||
- go get -u github.com/golang/dep/cmd/dep
|
|
||||||
install:
|
install:
|
||||||
- "$GOPATH/bin/dep ensure"
|
- "go mod download"
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- NAME=clash
|
- NAME=clash
|
||||||
- BINDIR=bin
|
- BINDIR=bin
|
||||||
|
- GO111MODULE=on
|
||||||
script:
|
script:
|
||||||
- go test ./...
|
- go test ./...
|
||||||
before_deploy: make -j releases
|
before_deploy: make -j releases
|
||||||
|
@ -2,11 +2,9 @@ FROM golang:latest as builder
|
|||||||
RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
|
RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
|
||||||
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
|
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
|
||||||
cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
|
cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
|
||||||
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh && \
|
WORKDIR /clash-src
|
||||||
mkdir -p /go/src/github.com/Dreamacro/clash
|
COPY . /clash-src
|
||||||
WORKDIR /go/src/github.com/Dreamacro/clash
|
RUN go mod download && \
|
||||||
COPY . /go/src/github.com/Dreamacro/clash
|
|
||||||
RUN dep ensure && \
|
|
||||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \
|
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \
|
||||||
chmod +x /clash
|
chmod +x /clash
|
||||||
|
|
||||||
|
149
Gopkg.lock
generated
149
Gopkg.lock
generated
@ -1,149 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:8fa55a6e302771a90a86ceae1ca3c0df4ef15d21092198e8313f61dde9eea963"
|
|
||||||
name = "github.com/Yawning/chacha20"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:444b82bfe35c83bbcaf84e310fb81a1f9ece03edfed586483c869e2c046aef69"
|
|
||||||
name = "github.com/eapache/queue"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:b9914f85d95a0968bafd1be1908ba29e2eafafd88d6fd13696be42bf5368c380"
|
|
||||||
name = "github.com/go-chi/chi"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "b5294d10673813fac8558e7f47242bc9e61b4c25"
|
|
||||||
version = "v3.3.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:dfa416a1bb8139f30832543340f972f65c0db9932034cb6a1b42c5ac615a3fb8"
|
|
||||||
name = "github.com/go-chi/cors"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "dba6525398619dead495962a916728e7ee2ca322"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:54d7b4b9ab2bb2bae35b55eea900bc8fe15cba05a95fc78bf7fd7b82a9a07afa"
|
|
||||||
name = "github.com/go-chi/render"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "3215478343fbc559bd3fc08f7031bb134d6bdad5"
|
|
||||||
version = "v1.0.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:ce579162ae1341f3e5ab30c0dce767f28b1eb6a81359aad01723f1ba6b4becdf"
|
|
||||||
name = "github.com/gofrs/uuid"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "370558f003bfe29580cd0f698d8640daccdcc45c"
|
|
||||||
version = "v3.1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:2e8bdbc8a11d716dab1bf66d326285d7e5c92fa4c996c1574ba1153e57534b85"
|
|
||||||
name = "github.com/oschwald/geoip2-golang"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "7118115686e16b77967cdbf55d1b944fe14ad312"
|
|
||||||
version = "v1.2.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:07e8589503b7ec22430dae6eed6f2c17e4249ab245574f4fd0ba8fc9c597d138"
|
|
||||||
name = "github.com/oschwald/maxminddb-golang"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "c5bec84d1963260297932a1b7a1753c8420717a7"
|
|
||||||
version = "v1.3.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:2bfa1a73654feb90893014b04779ce5205f3e19e843c0e32a89ea051d31f12d5"
|
|
||||||
name = "github.com/riobard/go-shadowsocks2"
|
|
||||||
packages = [
|
|
||||||
"core",
|
|
||||||
"shadowaead",
|
|
||||||
"shadowstream",
|
|
||||||
"socks",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "8346403248229fc7e10d7a259de8e9352a9d8830"
|
|
||||||
version = "v0.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc"
|
|
||||||
name = "github.com/sirupsen/logrus"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "3e01752db0189b9157070a0e1668a620f9a85da2"
|
|
||||||
version = "v1.0.6"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:d33888518d56c3f0cc9009594f56be4faf33ffff358fe10ff8e7e8cccf0e6617"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = [
|
|
||||||
"chacha20poly1305",
|
|
||||||
"hkdf",
|
|
||||||
"internal/chacha20",
|
|
||||||
"internal/subtle",
|
|
||||||
"poly1305",
|
|
||||||
"ssh/terminal",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:576f8d82185dc836ec6d10c0e5568dc4ff94e4d9f101d33ed5d6bae0cbba65b2"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = [
|
|
||||||
"cpu",
|
|
||||||
"unix",
|
|
||||||
"windows",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "ebe1bf3edb3325c393447059974de898d5133eb8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:975a4480c40f2d0b95e1f83d3ec1aa29a2774e80179e08a9a4ba2aab86721b23"
|
|
||||||
name = "gopkg.in/eapache/channels.v1"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "47238d5aae8c0fefd518ef2bee46290909cf8263"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:5abd6a22805b1919f6a6bca0ae58b13cef1f3412812f38569978f43ef02743d4"
|
|
||||||
name = "gopkg.in/ini.v1"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e"
|
|
||||||
version = "v1.38.2"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"github.com/go-chi/chi",
|
|
||||||
"github.com/go-chi/cors",
|
|
||||||
"github.com/go-chi/render",
|
|
||||||
"github.com/gofrs/uuid",
|
|
||||||
"github.com/oschwald/geoip2-golang",
|
|
||||||
"github.com/riobard/go-shadowsocks2/core",
|
|
||||||
"github.com/riobard/go-shadowsocks2/socks",
|
|
||||||
"github.com/sirupsen/logrus",
|
|
||||||
"golang.org/x/crypto/chacha20poly1305",
|
|
||||||
"gopkg.in/eapache/channels.v1",
|
|
||||||
"gopkg.in/ini.v1",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
70
Gopkg.toml
70
Gopkg.toml
@ -1,70 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/go-chi/chi"
|
|
||||||
version = "3.3.3"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/go-chi/cors"
|
|
||||||
version = "1.0.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/go-chi/render"
|
|
||||||
version = "1.0.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/gofrs/uuid"
|
|
||||||
version = "3.1.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/oschwald/geoip2-golang"
|
|
||||||
version = "1.2.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/riobard/go-shadowsocks2"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/sirupsen/logrus"
|
|
||||||
version = "1.0.6"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/eapache/channels.v1"
|
|
||||||
version = "1.1.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/ini.v1"
|
|
||||||
version = "1.38.2"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
83
README.md
83
README.md
@ -62,51 +62,82 @@ If you have Docker installed, you can run clash directly using `docker-compose`.
|
|||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
Configuration file at `$HOME/.config/clash/config.ini`
|
**NOTE: after v0.8.0, clash using yaml as configuration file**
|
||||||
|
|
||||||
|
The default configuration directory is `$HOME/.config/clash`
|
||||||
|
|
||||||
|
The name of the configuration file is `config.yml`
|
||||||
|
|
||||||
|
If you want to use another directory, you can use `-d` to control the configuration directory
|
||||||
|
|
||||||
|
For example, you can use the current directory as the configuration directory
|
||||||
|
|
||||||
|
```sh
|
||||||
|
clash -d .
|
||||||
|
```
|
||||||
|
|
||||||
Below is a simple demo configuration file:
|
Below is a simple demo configuration file:
|
||||||
|
|
||||||
```ini
|
```yml
|
||||||
[General]
|
# port of HTTP
|
||||||
port = 7890
|
port: 7890
|
||||||
socks-port = 7891
|
|
||||||
|
# port of SOCKS5
|
||||||
|
socks-port: 7891
|
||||||
|
|
||||||
# redir proxy for Linux and macOS
|
# redir proxy for Linux and macOS
|
||||||
redir-port = 7892
|
# redir-port: 7892
|
||||||
|
|
||||||
|
allow-lan: false
|
||||||
|
|
||||||
|
# Rule / Global/ Direct (default is Rule)
|
||||||
|
mode: Rule
|
||||||
|
|
||||||
|
# set log level to stdout (default is info)
|
||||||
|
# info / warning / error / debug
|
||||||
|
log-level: info
|
||||||
|
|
||||||
# A RESTful API for clash
|
# A RESTful API for clash
|
||||||
external-controller = 127.0.0.1:8080
|
external-controller: 127.0.0.1:9090
|
||||||
|
|
||||||
[Proxy]
|
# Secret for RESTful API (Optional)
|
||||||
# name = ss, server, port, cipher, password(, obfs=tls/http, obfs-host=bing.com)
|
secret: ""
|
||||||
|
|
||||||
|
Proxy:
|
||||||
|
|
||||||
|
# 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
|
||||||
ss1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password
|
# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305
|
||||||
ss2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password
|
- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" }
|
||||||
|
- { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com }
|
||||||
|
|
||||||
# name = vmess, server, port, uuid, alterId, cipher(, tls=true)
|
# vmess
|
||||||
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
|
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
|
||||||
vmess1 = vmess, server, port, uuid, 32, auto, tls=true
|
- { name: "vmess1", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto }
|
||||||
|
- { name: "vmess2", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
|
||||||
|
|
||||||
# name = socks5, server, port
|
# socks5
|
||||||
socks = socks5, server, port
|
- { name: "socks", type: socks5, server: server, port: 443 }
|
||||||
|
|
||||||
[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 = url-test, [proxies], url, interval(second)
|
- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 }
|
||||||
auto = url-test, ss1, ss2, http://www.google.com/generate_204, 300
|
|
||||||
|
# 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 }
|
||||||
|
|
||||||
# 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 = select, [proxies]
|
- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] }
|
||||||
Proxy = select, ss1, ss2, auto
|
|
||||||
|
|
||||||
[Rule]
|
Rule:
|
||||||
DOMAIN-SUFFIX,google.com,Proxy
|
- DOMAIN-SUFFIX,google.com,Proxy
|
||||||
DOMAIN-KEYWORD,google,Proxy
|
- DOMAIN-KEYWORD,google,Proxy
|
||||||
DOMAIN-SUFFIX,ad.com,REJECT
|
- DOMAIN-SUFFIX,ad.com,REJECT
|
||||||
GEOIP,CN,DIRECT
|
- GEOIP,CN,DIRECT
|
||||||
FINAL,,Proxy # note: there is two ","
|
# note: there is two ","
|
||||||
|
- FINAL,,Proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
|
|
||||||
// HTTPAdapter is a adapter for HTTP connection
|
// HTTPAdapter is a adapter for HTTP connection
|
||||||
type HTTPAdapter struct {
|
type HTTPAdapter struct {
|
||||||
addr *C.Addr
|
metadata *C.Metadata
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
R *http.Request
|
R *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close HTTP connection
|
// Close HTTP connection
|
||||||
@ -20,9 +20,9 @@ func (h *HTTPAdapter) Close() {
|
|||||||
h.conn.Close()
|
h.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addr return destination address
|
// Metadata return destination metadata
|
||||||
func (h *HTTPAdapter) Addr() *C.Addr {
|
func (h *HTTPAdapter) Metadata() *C.Metadata {
|
||||||
return h.addr
|
return h.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conn return raw net.Conn of HTTP
|
// Conn return raw net.Conn of HTTP
|
||||||
@ -33,9 +33,9 @@ func (h *HTTPAdapter) Conn() net.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 {
|
||||||
return &HTTPAdapter{
|
return &HTTPAdapter{
|
||||||
addr: parseHTTPAddr(request),
|
metadata: parseHTTPAddr(request),
|
||||||
R: request,
|
R: request,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ 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 {
|
||||||
return &SocketAdapter{
|
return &SocketAdapter{
|
||||||
addr: parseHTTPAddr(request),
|
metadata: parseHTTPAddr(request),
|
||||||
conn: conn,
|
conn: conn,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,13 +4,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"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
|
conn net.Conn
|
||||||
addr *C.Addr
|
metadata *C.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close socks and redir connection
|
// Close socks and redir connection
|
||||||
@ -18,9 +18,9 @@ func (s *SocketAdapter) Close() {
|
|||||||
s.conn.Close()
|
s.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addr return destination address
|
// Metadata return destination metadata
|
||||||
func (s *SocketAdapter) Addr() *C.Addr {
|
func (s *SocketAdapter) Metadata() *C.Metadata {
|
||||||
return s.addr
|
return s.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conn return raw net.Conn
|
// Conn return raw net.Conn
|
||||||
@ -31,7 +31,7 @@ func (s *SocketAdapter) Conn() net.Conn {
|
|||||||
// NewSocket is SocketAdapter generator
|
// NewSocket is SocketAdapter generator
|
||||||
func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter {
|
func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter {
|
||||||
return &SocketAdapter{
|
return &SocketAdapter{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
addr: parseSocksAddr(target),
|
metadata: parseSocksAddr(target),
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,10 +6,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseSocksAddr(target socks.Addr) *C.Addr {
|
func parseSocksAddr(target socks.Addr) *C.Metadata {
|
||||||
var host, port string
|
var host, port string
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ func parseSocksAddr(target socks.Addr) *C.Addr {
|
|||||||
port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &C.Addr{
|
return &C.Metadata{
|
||||||
NetWork: C.TCP,
|
NetWork: C.TCP,
|
||||||
AddrType: int(target[0]),
|
AddrType: int(target[0]),
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -38,7 +38,7 @@ func parseSocksAddr(target socks.Addr) *C.Addr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHTTPAddr(request *http.Request) *C.Addr {
|
func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||||
host := request.URL.Hostname()
|
host := request.URL.Hostname()
|
||||||
port := request.URL.Port()
|
port := request.URL.Port()
|
||||||
if port == "" {
|
if port == "" {
|
||||||
@ -61,7 +61,7 @@ func parseHTTPAddr(request *http.Request) *C.Addr {
|
|||||||
addType = socks.AtypIPv4
|
addType = socks.AtypIPv4
|
||||||
}
|
}
|
||||||
|
|
||||||
return &C.Addr{
|
return &C.Metadata{
|
||||||
NetWork: C.TCP,
|
NetWork: C.TCP,
|
||||||
AddrType: addType,
|
AddrType: addType,
|
||||||
Host: host,
|
Host: host,
|
@ -24,15 +24,15 @@ func (d *DirectAdapter) Conn() net.Conn {
|
|||||||
type Direct struct{}
|
type Direct struct{}
|
||||||
|
|
||||||
func (d *Direct) Name() string {
|
func (d *Direct) Name() string {
|
||||||
return "Direct"
|
return "DIRECT"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) Type() C.AdapterType {
|
func (d *Direct) Type() C.AdapterType {
|
||||||
return C.Direct
|
return C.Direct
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", net.JoinHostPort(addr.String(), addr.Port))
|
c, err := net.Dial("tcp", net.JoinHostPort(metadata.String(), metadata.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
136
adapters/outbound/fallback.go
Normal file
136
adapters/outbound/fallback.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxy struct {
|
||||||
|
RawProxy C.Proxy
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fallback struct {
|
||||||
|
name string
|
||||||
|
proxies []*proxy
|
||||||
|
rawURL string
|
||||||
|
interval time.Duration
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FallbackOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Proxies []string `proxy:"proxies"`
|
||||||
|
URL string `proxy:"url"`
|
||||||
|
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 {
|
||||||
|
_, proxy := f.findNextValidProxy(0)
|
||||||
|
if proxy != nil {
|
||||||
|
return proxy.RawProxy.Name()
|
||||||
|
}
|
||||||
|
return f.proxies[0].RawProxy.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
|
idx := 0
|
||||||
|
var proxy *proxy
|
||||||
|
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() {
|
||||||
|
f.done <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fallback) loop() {
|
||||||
|
tick := time.NewTicker(f.interval)
|
||||||
|
go f.validTest()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
go f.validTest()
|
||||||
|
case <-f.done:
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fallback) findNextValidProxy(start int) (int, *proxy) {
|
||||||
|
for i := start; i < len(f.proxies); i++ {
|
||||||
|
if f.proxies[i].Valid {
|
||||||
|
return i, f.proxies[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fallback) validTest() {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(f.proxies))
|
||||||
|
|
||||||
|
for _, p := range f.proxies {
|
||||||
|
go func(p *proxy) {
|
||||||
|
_, err := DelayTest(p.RawProxy, f.rawURL)
|
||||||
|
p.Valid = err == nil
|
||||||
|
wg.Done()
|
||||||
|
}(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
|
||||||
|
_, err := urlToMetadata(option.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(proxies) < 1 {
|
||||||
|
return nil, errors.New("The number of proxies cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
name: option.Name,
|
||||||
|
proxies: warpperProxies,
|
||||||
|
rawURL: option.URL,
|
||||||
|
interval: interval,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go Fallback.loop()
|
||||||
|
return Fallback, nil
|
||||||
|
}
|
@ -25,14 +25,14 @@ type Reject struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reject) Name() string {
|
func (r *Reject) Name() string {
|
||||||
return "Reject"
|
return "REJECT"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reject) Type() C.AdapterType {
|
func (r *Reject) Type() C.AdapterType {
|
||||||
return C.Reject
|
return C.Reject
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
return &RejectAdapter{conn: &NopConn{}}, nil
|
return &RejectAdapter{conn: &NopConn{}}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,11 @@ type Selector struct {
|
|||||||
proxies map[string]C.Proxy
|
proxies map[string]C.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SelectorOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Proxies []string `proxy:"proxies"`
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Selector) Name() string {
|
func (s *Selector) Name() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
@ -21,8 +26,8 @@ func (s *Selector) Type() C.AdapterType {
|
|||||||
return C.Selector
|
return C.Selector
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
return s.selected.Generator(addr)
|
return s.selected.Generator(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) Now() string {
|
func (s *Selector) Now() string {
|
||||||
@ -47,21 +52,20 @@ func (s *Selector) Set(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(name string, proxies map[string]C.Proxy) (*Selector, error) {
|
func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return nil, errors.New("Provide at least one proxy")
|
return nil, errors.New("Provide at least one proxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping := make(map[string]C.Proxy)
|
mapping := make(map[string]C.Proxy)
|
||||||
var init string
|
for _, proxy := range proxies {
|
||||||
for k, v := range proxies {
|
mapping[proxy.Name()] = proxy
|
||||||
mapping[k] = v
|
|
||||||
init = k
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Selector{
|
s := &Selector{
|
||||||
name: name,
|
name: name,
|
||||||
proxies: mapping,
|
proxies: mapping,
|
||||||
selected: proxies[init],
|
selected: proxies[0],
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
@ -4,14 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/simple-obfs"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/simple-obfs"
|
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||||
"github.com/riobard/go-shadowsocks2/core"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShadowsocksAdapter is a shadowsocks adapter
|
// ShadowsocksAdapter is a shadowsocks adapter
|
||||||
@ -36,6 +35,16 @@ type ShadowSocks struct {
|
|||||||
cipher core.Cipher
|
cipher core.Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShadowSocksOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Server string `proxy:"server"`
|
||||||
|
Port int `proxy:"port"`
|
||||||
|
Password string `proxy:"password"`
|
||||||
|
Cipher string `proxy:"cipher"`
|
||||||
|
Obfs string `proxy:"obfs,omitempty"`
|
||||||
|
ObfsHost string `proxy:"obfs-host,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) Name() string {
|
func (ss *ShadowSocks) Name() string {
|
||||||
return ss.name
|
return ss.name
|
||||||
}
|
}
|
||||||
@ -44,7 +53,7 @@ func (ss *ShadowSocks) Type() C.AdapterType {
|
|||||||
return C.Shadowsocks
|
return C.Shadowsocks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", ss.server)
|
c, err := net.Dial("tcp", ss.server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", ss.server)
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
@ -58,67 +67,50 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro
|
|||||||
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
|
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
|
||||||
}
|
}
|
||||||
c = ss.cipher.StreamConn(c)
|
c = ss.cipher.StreamConn(c)
|
||||||
_, err = c.Write(serializesSocksAddr(addr))
|
_, err = c.Write(serializesSocksAddr(metadata))
|
||||||
return &ShadowsocksAdapter{conn: c}, err
|
return &ShadowsocksAdapter{conn: c}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowSocks(name string, ssURL string, option map[string]string) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
var key []byte
|
server := fmt.Sprintf("%s:%d", option.Server, option.Port)
|
||||||
server, cipher, password, _ := parseURL(ssURL)
|
cipher := option.Cipher
|
||||||
ciph, err := core.PickCipher(cipher, key, password)
|
password := option.Password
|
||||||
|
ciph, err := core.PickCipher(cipher, nil, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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 := ""
|
obfs := option.Obfs
|
||||||
obfsHost := "bing.com"
|
obfsHost := "bing.com"
|
||||||
if value, ok := option["obfs"]; ok {
|
if option.ObfsHost != "" {
|
||||||
obfs = value
|
obfsHost = option.ObfsHost
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := option["obfs-host"]; ok {
|
|
||||||
obfsHost = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ShadowSocks{
|
return &ShadowSocks{
|
||||||
server: server,
|
server: server,
|
||||||
name: name,
|
name: option.Name,
|
||||||
cipher: ciph,
|
cipher: ciph,
|
||||||
obfs: obfs,
|
obfs: obfs,
|
||||||
obfsHost: obfsHost,
|
obfsHost: obfsHost,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseURL(s string) (addr, cipher, password string, err error) {
|
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||||
u, err := url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addr = u.Host
|
|
||||||
if u.User != nil {
|
|
||||||
cipher = u.User.Username()
|
|
||||||
password, _ = u.User.Password()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializesSocksAddr(addr *C.Addr) []byte {
|
|
||||||
var buf [][]byte
|
var buf [][]byte
|
||||||
aType := uint8(addr.AddrType)
|
aType := uint8(metadata.AddrType)
|
||||||
p, _ := strconv.Atoi(addr.Port)
|
p, _ := strconv.Atoi(metadata.Port)
|
||||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||||
switch addr.AddrType {
|
switch metadata.AddrType {
|
||||||
case socks.AtypDomainName:
|
case socks.AtypDomainName:
|
||||||
len := uint8(len(addr.Host))
|
len := uint8(len(metadata.Host))
|
||||||
host := []byte(addr.Host)
|
host := []byte(metadata.Host)
|
||||||
buf = [][]byte{{aType, len}, host, port}
|
buf = [][]byte{{aType, len}, host, port}
|
||||||
case socks.AtypIPv4:
|
case socks.AtypIPv4:
|
||||||
host := addr.IP.To4()
|
host := metadata.IP.To4()
|
||||||
buf = [][]byte{{aType}, host, port}
|
buf = [][]byte{{aType}, host, port}
|
||||||
case socks.AtypIPv6:
|
case socks.AtypIPv6:
|
||||||
host := addr.IP.To16()
|
host := metadata.IP.To16()
|
||||||
buf = [][]byte{{aType}, host, port}
|
buf = [][]byte{{aType}, host, port}
|
||||||
}
|
}
|
||||||
return bytes.Join(buf, []byte(""))
|
return bytes.Join(buf, nil)
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socks5Adapter is a shadowsocks adapter
|
// Socks5Adapter is a shadowsocks adapter
|
||||||
@ -31,6 +31,12 @@ type Socks5 struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Socks5Option struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Server string `proxy:"server"`
|
||||||
|
Port int `proxy:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *Socks5) Name() string {
|
func (ss *Socks5) Name() string {
|
||||||
return ss.name
|
return ss.name
|
||||||
}
|
}
|
||||||
@ -39,19 +45,19 @@ func (ss *Socks5) Type() C.AdapterType {
|
|||||||
return C.Socks5
|
return C.Socks5
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", ss.addr)
|
c, err := net.Dial("tcp", ss.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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.sharkHand(addr, c); err != nil {
|
if err := ss.shakeHand(metadata, c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Socks5Adapter{conn: c}, nil
|
return &Socks5Adapter{conn: c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) sharkHand(addr *C.Addr, rw io.ReadWriter) error {
|
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||||
buf := make([]byte, socks.MaxAddrLen)
|
buf := make([]byte, socks.MaxAddrLen)
|
||||||
|
|
||||||
// VER, CMD, RSV
|
// VER, CMD, RSV
|
||||||
@ -71,7 +77,7 @@ func (ss *Socks5) sharkHand(addr *C.Addr, rw io.ReadWriter) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VER, CMD, RSV, ADDR
|
// VER, CMD, RSV, ADDR
|
||||||
if _, err := rw.Write(bytes.Join([][]byte{[]byte{5, 1, 0}, serializesSocksAddr(addr)}, []byte(""))); err != nil {
|
if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(metadata)}, []byte(""))); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,9 +88,9 @@ func (ss *Socks5) sharkHand(addr *C.Addr, rw io.ReadWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(name, addr string) *Socks5 {
|
func NewSocks5(option Socks5Option) *Socks5 {
|
||||||
return &Socks5{
|
return &Socks5{
|
||||||
addr: addr,
|
addr: fmt.Sprintf("%s:%d", option.Server, option.Port),
|
||||||
name: name,
|
name: option.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package adapters
|
package adapters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -8,12 +9,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type URLTest struct {
|
type URLTest struct {
|
||||||
name string
|
name string
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
rawURL string
|
rawURL string
|
||||||
fast C.Proxy
|
fast C.Proxy
|
||||||
delay time.Duration
|
interval time.Duration
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLTestOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Proxies []string `proxy:"proxies"`
|
||||||
|
URL string `proxy:"url"`
|
||||||
|
Interval int `proxy:"interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) Name() string {
|
func (u *URLTest) Name() string {
|
||||||
@ -28,8 +36,8 @@ func (u *URLTest) Now() string {
|
|||||||
return u.fast.Name()
|
return u.fast.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
return u.fast.Generator(addr)
|
return u.fast.Generator(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) Close() {
|
func (u *URLTest) Close() {
|
||||||
@ -37,7 +45,7 @@ func (u *URLTest) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) loop() {
|
func (u *URLTest) loop() {
|
||||||
tick := time.NewTicker(u.delay)
|
tick := time.NewTicker(u.interval)
|
||||||
go u.speedTest()
|
go u.speedTest()
|
||||||
Loop:
|
Loop:
|
||||||
for {
|
for {
|
||||||
@ -55,7 +63,7 @@ func (u *URLTest) speedTest() {
|
|||||||
wg.Add(len(u.proxies))
|
wg.Add(len(u.proxies))
|
||||||
c := make(chan interface{})
|
c := make(chan interface{})
|
||||||
fast := selectFast(c)
|
fast := selectFast(c)
|
||||||
timer := time.NewTimer(u.delay)
|
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) {
|
||||||
@ -83,19 +91,23 @@ func (u *URLTest) speedTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) {
|
func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
|
||||||
_, err := urlToAddr(rawURL)
|
_, err := urlToMetadata(option.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(proxies) < 1 {
|
||||||
|
return nil, errors.New("The number of proxies cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
interval := time.Duration(option.Interval) * time.Second
|
||||||
urlTest := &URLTest{
|
urlTest := &URLTest{
|
||||||
name: name,
|
name: option.Name,
|
||||||
proxies: proxies[:],
|
proxies: proxies[:],
|
||||||
rawURL: rawURL,
|
rawURL: option.URL,
|
||||||
fast: proxies[0],
|
fast: proxies[0],
|
||||||
delay: delay,
|
interval: interval,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go urlTest.loop()
|
go urlTest.loop()
|
||||||
return urlTest, nil
|
return urlTest, nil
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// DelayTest get the delay for the specified URL
|
// DelayTest get the delay for the specified URL
|
||||||
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
|
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
|
||||||
addr, err := urlToAddr(url)
|
addr, err := urlToMetadata(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -34,16 +34,16 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
|
|||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
client := http.Client{Transport: transport}
|
client := http.Client{Transport: transport}
|
||||||
req, err := client.Get(url)
|
resp, err := client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Body.Close()
|
resp.Body.Close()
|
||||||
t = int16(time.Since(start) / time.Millisecond)
|
t = int16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func urlToAddr(rawURL string) (addr C.Addr, 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 {
|
||||||
return
|
return
|
||||||
@ -61,7 +61,7 @@ func urlToAddr(rawURL string) (addr C.Addr, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addr = C.Addr{
|
addr = C.Metadata{
|
||||||
AddrType: C.AtypDomainName,
|
AddrType: C.AtypDomainName,
|
||||||
Host: u.Hostname(),
|
Host: u.Hostname(),
|
||||||
IP: nil,
|
IP: nil,
|
||||||
@ -88,6 +88,6 @@ func selectFast(in chan interface{}) chan interface{} {
|
|||||||
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)
|
||||||
tcp.SetKeepAlivePeriod(3 * time.Minute)
|
tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/vmess"
|
"github.com/Dreamacro/clash/component/vmess"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,6 +30,16 @@ type Vmess struct {
|
|||||||
client *vmess.Client
|
client *vmess.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VmessOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Server string `proxy:"server"`
|
||||||
|
Port int `proxy:"port"`
|
||||||
|
UUID string `proxy:"uuid"`
|
||||||
|
AlterID int `proxy:"alterId"`
|
||||||
|
Cipher string `proxy:"cipher"`
|
||||||
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *Vmess) Name() string {
|
func (ss *Vmess) Name() string {
|
||||||
return ss.name
|
return ss.name
|
||||||
}
|
}
|
||||||
@ -38,55 +48,55 @@ func (ss *Vmess) Type() C.AdapterType {
|
|||||||
return C.Vmess
|
return C.Vmess
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Vmess) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) {
|
func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
|
||||||
c, err := net.Dial("tcp", ss.server)
|
c, err := net.Dial("tcp", ss.server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", ss.server)
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c = ss.client.New(c, parseVmessAddr(addr))
|
c = ss.client.New(c, parseVmessAddr(metadata))
|
||||||
return &VmessAdapter{conn: c}, err
|
return &VmessAdapter{conn: c}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVmess(name string, server string, uuid string, alterID uint16, security string, option map[string]string) (*Vmess, error) {
|
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
security = strings.ToLower(security)
|
security := strings.ToLower(option.Cipher)
|
||||||
client, err := vmess.NewClient(vmess.Config{
|
client, err := vmess.NewClient(vmess.Config{
|
||||||
UUID: uuid,
|
UUID: option.UUID,
|
||||||
AlterID: alterID,
|
AlterID: uint16(option.AlterID),
|
||||||
Security: security,
|
Security: security,
|
||||||
TLS: option["tls"] == "true",
|
TLS: option.TLS,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Vmess{
|
return &Vmess{
|
||||||
name: name,
|
name: option.Name,
|
||||||
server: server,
|
server: fmt.Sprintf("%s:%d", option.Server, option.Port),
|
||||||
client: client,
|
client: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVmessAddr(info *C.Addr) *vmess.DstAddr {
|
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
||||||
var addrType byte
|
var addrType byte
|
||||||
var addr []byte
|
var addr []byte
|
||||||
switch info.AddrType {
|
switch metadata.AddrType {
|
||||||
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[:], info.IP.To4())
|
copy(addr[:], metadata.IP.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[:], info.IP.To16())
|
copy(addr[:], metadata.IP.To16())
|
||||||
case C.AtypDomainName:
|
case C.AtypDomainName:
|
||||||
addrType = byte(vmess.AtypDomainName)
|
addrType = byte(vmess.AtypDomainName)
|
||||||
addr = make([]byte, len(info.Host)+1)
|
addr = make([]byte, len(metadata.Host)+1)
|
||||||
addr[0] = byte(len(info.Host))
|
addr[0] = byte(len(metadata.Host))
|
||||||
copy(addr[1:], []byte(info.Host))
|
copy(addr[1:], []byte(metadata.Host))
|
||||||
}
|
}
|
||||||
|
|
||||||
port, _ := strconv.Atoi(info.Port)
|
port, _ := strconv.Atoi(metadata.Port)
|
||||||
return &vmess.DstAddr{
|
return &vmess.DstAddr{
|
||||||
AddrType: addrType,
|
AddrType: addrType,
|
||||||
Addr: addr,
|
Addr: addr,
|
160
common/structure/structure.go
Normal file
160
common/structure/structure.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package structure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is the configuration that is used to create a new decoder
|
||||||
|
type Option struct {
|
||||||
|
TagName string
|
||||||
|
WeaklyTypedInput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder is the core of structure
|
||||||
|
type Decoder struct {
|
||||||
|
option *Option
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder return a Decoder by Option
|
||||||
|
func NewDecoder(option Option) *Decoder {
|
||||||
|
if option.TagName == "" {
|
||||||
|
option.TagName = "structure"
|
||||||
|
}
|
||||||
|
return &Decoder{option: &option}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode transform a map[string]interface{} to a struct
|
||||||
|
func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
|
||||||
|
if reflect.TypeOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("Decode must recive a ptr struct")
|
||||||
|
}
|
||||||
|
t := reflect.TypeOf(dst).Elem()
|
||||||
|
v := reflect.ValueOf(dst).Elem()
|
||||||
|
for idx := 0; idx < v.NumField(); idx++ {
|
||||||
|
field := t.Field(idx)
|
||||||
|
|
||||||
|
tag := field.Tag.Get(d.option.TagName)
|
||||||
|
str := strings.SplitN(tag, ",", 2)
|
||||||
|
key := str[0]
|
||||||
|
omitempty := false
|
||||||
|
if len(str) > 1 {
|
||||||
|
omitempty = str[1] == "omitempty"
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok := src[key]
|
||||||
|
if !ok {
|
||||||
|
if omitempty {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("key %s missing", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := d.decode(key, value, v.Field(idx))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error {
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
return d.decodeInt(name, data, val)
|
||||||
|
case reflect.String:
|
||||||
|
return d.decodeString(name, data, val)
|
||||||
|
case reflect.Bool:
|
||||||
|
return d.decodeBool(name, data, val)
|
||||||
|
case reflect.Slice:
|
||||||
|
return d.decodeSlice(name, data, val)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("type %s not support", val.Kind().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) (err error) {
|
||||||
|
dataVal := reflect.ValueOf(data)
|
||||||
|
kind := dataVal.Kind()
|
||||||
|
switch {
|
||||||
|
case kind == reflect.Int:
|
||||||
|
val.SetInt(dataVal.Int())
|
||||||
|
case kind == reflect.String && d.option.WeaklyTypedInput:
|
||||||
|
var i int64
|
||||||
|
i, err = strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
|
||||||
|
if err == nil {
|
||||||
|
val.SetInt(i)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("cannot parse '%s' as int: %s", name, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||||
|
name, val.Type(), dataVal.Type(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) (err error) {
|
||||||
|
dataVal := reflect.ValueOf(data)
|
||||||
|
kind := dataVal.Kind()
|
||||||
|
switch {
|
||||||
|
case kind == reflect.String:
|
||||||
|
val.SetString(dataVal.String())
|
||||||
|
case kind == reflect.Int && d.option.WeaklyTypedInput:
|
||||||
|
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"'%s' expected type'%s', got unconvertible type '%s'",
|
||||||
|
name, val.Type(), dataVal.Type(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (err error) {
|
||||||
|
dataVal := reflect.ValueOf(data)
|
||||||
|
kind := dataVal.Kind()
|
||||||
|
switch {
|
||||||
|
case kind == reflect.Bool:
|
||||||
|
val.SetBool(dataVal.Bool())
|
||||||
|
case kind == reflect.Int && d.option.WeaklyTypedInput:
|
||||||
|
val.SetBool(dataVal.Int() != 0)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"'%s' expected type'%s', got unconvertible type '%s'",
|
||||||
|
name, val.Type(), dataVal.Type(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
|
||||||
|
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||||
|
valType := val.Type()
|
||||||
|
valElemType := valType.Elem()
|
||||||
|
|
||||||
|
if dataVal.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("'%s' is not a slice", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
valSlice := val
|
||||||
|
for i := 0; i < dataVal.Len(); i++ {
|
||||||
|
currentData := dataVal.Index(i).Interface()
|
||||||
|
for valSlice.Len() <= i {
|
||||||
|
valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
|
||||||
|
}
|
||||||
|
currentField := valSlice.Index(i)
|
||||||
|
|
||||||
|
fieldName := fmt.Sprintf("%s[%d]", name, i)
|
||||||
|
if err := d.decode(fieldName, currentData, currentField); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val.Set(valSlice)
|
||||||
|
return nil
|
||||||
|
}
|
141
common/structure/structure_test.go
Normal file
141
common/structure/structure_test.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package structure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var decoder = NewDecoder(Option{TagName: "test"})
|
||||||
|
var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true})
|
||||||
|
|
||||||
|
type Baz struct {
|
||||||
|
Foo int `test:"foo"`
|
||||||
|
Bar string `test:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BazSlice struct {
|
||||||
|
Foo int `test:"foo"`
|
||||||
|
Bar []string `test:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BazOptional struct {
|
||||||
|
Foo int `test:"foo,omitempty"`
|
||||||
|
Bar string `test:"bar,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_Basic(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
"bar": "test",
|
||||||
|
"extra": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
goal := &Baz{
|
||||||
|
Foo: 1,
|
||||||
|
Bar: "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Baz{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s, goal) {
|
||||||
|
t.Fatalf("bad: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_Slice(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
"bar": []string{"one", "two"},
|
||||||
|
}
|
||||||
|
|
||||||
|
goal := &BazSlice{
|
||||||
|
Foo: 1,
|
||||||
|
Bar: []string{"one", "two"},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &BazSlice{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s, goal) {
|
||||||
|
t.Fatalf("bad: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_Optional(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
goal := &BazOptional{
|
||||||
|
Foo: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &BazOptional{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s, goal) {
|
||||||
|
t.Fatalf("bad: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_MissingKey(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Baz{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should throw error: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_ParamError(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{}
|
||||||
|
s := Baz{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should throw error: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_SliceTypeError(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": 1,
|
||||||
|
"bar": []int{1, 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &BazSlice{}
|
||||||
|
err := decoder.Decode(rawMap, s)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should throw error: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructure_WeakType(t *testing.T) {
|
||||||
|
rawMap := map[string]interface{}{
|
||||||
|
"foo": "1",
|
||||||
|
"bar": []int{1},
|
||||||
|
}
|
||||||
|
|
||||||
|
goal := &BazSlice{
|
||||||
|
Foo: 1,
|
||||||
|
Bar: []string{"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &BazSlice{}
|
||||||
|
err := weakTypeDecoder.Decode(rawMap, s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s, goal) {
|
||||||
|
t.Fatalf("bad: %#v", s)
|
||||||
|
}
|
||||||
|
}
|
306
config/config.go
306
config/config.go
@ -1,21 +1,21 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/remote"
|
adapters "github.com/Dreamacro/clash/adapters/outbound"
|
||||||
|
"github.com/Dreamacro/clash/common/observable"
|
||||||
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/observable"
|
|
||||||
R "github.com/Dreamacro/clash/rules"
|
R "github.com/Dreamacro/clash/rules"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/ini.v1"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -41,6 +41,22 @@ type ProxyConfig struct {
|
|||||||
AllowLan *bool
|
AllowLan *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawConfig is raw config struct
|
||||||
|
type RawConfig struct {
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
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
|
||||||
@ -70,17 +86,30 @@ func (c *Config) Report() chan<- interface{} {
|
|||||||
return c.reportCh
|
return c.reportCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) readConfig() (*ini.File, error) {
|
func (c *Config) readConfig() (*RawConfig, error) {
|
||||||
if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ini.LoadSources(
|
data, err := ioutil.ReadFile(C.Path.Config())
|
||||||
ini.LoadOptions{
|
if err != nil {
|
||||||
AllowBooleanKeys: true,
|
return nil, err
|
||||||
UnparseableSections: []string{"Rule"},
|
}
|
||||||
},
|
|
||||||
C.ConfigPath,
|
if len(data) == 0 {
|
||||||
)
|
return nil, fmt.Errorf("Configuration file %s is empty", C.Path.Config())
|
||||||
|
}
|
||||||
|
|
||||||
|
// config with some default value
|
||||||
|
rawConfig := &RawConfig{
|
||||||
|
AllowLan: false,
|
||||||
|
Mode: Rule.String(),
|
||||||
|
LogLevel: C.INFO.String(),
|
||||||
|
Rule: []string{},
|
||||||
|
Proxy: []map[string]interface{}{},
|
||||||
|
ProxyGroup: []map[string]interface{}{},
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal([]byte(data), &rawConfig)
|
||||||
|
return rawConfig, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse config
|
// Parse config
|
||||||
@ -138,15 +167,13 @@ func (c *Config) UpdateRules() error {
|
|||||||
return c.parseRules(cfg)
|
return c.parseRules(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) parseGeneral(cfg *ini.File) error {
|
func (c *Config) parseGeneral(cfg *RawConfig) error {
|
||||||
general := cfg.Section("General")
|
port := cfg.Port
|
||||||
|
socksPort := cfg.SocksPort
|
||||||
port := general.Key("port").RangeInt(0, 1, 65535)
|
redirPort := cfg.RedirPort
|
||||||
socksPort := general.Key("socks-port").RangeInt(0, 1, 65535)
|
allowLan := cfg.AllowLan
|
||||||
redirPort := general.Key("redir-port").RangeInt(0, 1, 65535)
|
logLevelString := cfg.LogLevel
|
||||||
allowLan := general.Key("allow-lan").MustBool()
|
modeString := cfg.Mode
|
||||||
logLevelString := general.Key("log-level").MustString(C.INFO.String())
|
|
||||||
modeString := general.Key("mode").MustString(Rule.String())
|
|
||||||
|
|
||||||
mode, exist := ModeMapping[modeString]
|
mode, exist := ModeMapping[modeString]
|
||||||
if !exist {
|
if !exist {
|
||||||
@ -167,8 +194,9 @@ func (c *Config) parseGeneral(cfg *ini.File) error {
|
|||||||
LogLevel: logLevel,
|
LogLevel: logLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
if restAddr := general.Key("external-controller").String(); restAddr != "" {
|
if restAddr := cfg.ExternalController; restAddr != "" {
|
||||||
c.event <- &Event{Type: "external-controller", Payload: restAddr}
|
c.event <- &Event{Type: "external-controller", Payload: restAddr}
|
||||||
|
c.event <- &Event{Type: "secret", Payload: cfg.Secret}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.UpdateGeneral(*c.general)
|
c.UpdateGeneral(*c.general)
|
||||||
@ -209,125 +237,145 @@ func (c *Config) UpdateProxy(pc ProxyConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) parseProxies(cfg *ini.File) error {
|
func (c *Config) parseProxies(cfg *RawConfig) error {
|
||||||
proxies := make(map[string]C.Proxy)
|
proxies := make(map[string]C.Proxy)
|
||||||
proxiesConfig := cfg.Section("Proxy")
|
proxiesConfig := cfg.Proxy
|
||||||
groupsConfig := cfg.Section("Proxy Group")
|
groupsConfig := cfg.ProxyGroup
|
||||||
|
|
||||||
// parse proxy
|
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||||
for _, key := range proxiesConfig.Keys() {
|
|
||||||
proxy := key.Strings(",")
|
|
||||||
if len(proxy) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch proxy[0] {
|
|
||||||
// ss, server, port, cipter, password
|
|
||||||
case "ss":
|
|
||||||
if len(proxy) < 5 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2])
|
|
||||||
option := parseOptions(5, proxy...)
|
|
||||||
ss, err := adapters.NewShadowSocks(key.Name(), ssURL, option)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
proxies[key.Name()] = ss
|
|
||||||
// socks5, server, port
|
|
||||||
case "socks5":
|
|
||||||
if len(proxy) < 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2])
|
|
||||||
socks5 := adapters.NewSocks5(key.Name(), addr)
|
|
||||||
proxies[key.Name()] = socks5
|
|
||||||
// vmess, server, port, uuid, alterId, security
|
|
||||||
case "vmess":
|
|
||||||
if len(proxy) < 6 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2])
|
|
||||||
alterID, err := strconv.Atoi(proxy[4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
option := parseOptions(6, proxy...)
|
|
||||||
vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5], option)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
proxies[key.Name()] = vmess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse proxy group
|
|
||||||
for _, key := range groupsConfig.Keys() {
|
|
||||||
rule := strings.Split(key.Value(), ",")
|
|
||||||
rule = trimArr(rule)
|
|
||||||
switch rule[0] {
|
|
||||||
case "url-test":
|
|
||||||
if len(rule) < 4 {
|
|
||||||
return fmt.Errorf("URLTest need more than 4 param")
|
|
||||||
}
|
|
||||||
proxyNames := rule[1 : len(rule)-2]
|
|
||||||
delay, _ := strconv.Atoi(rule[len(rule)-1])
|
|
||||||
url := rule[len(rule)-2]
|
|
||||||
var ps []C.Proxy
|
|
||||||
for _, name := range proxyNames {
|
|
||||||
if p, ok := proxies[name]; ok {
|
|
||||||
ps = append(ps, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Config error: %s", err.Error())
|
|
||||||
}
|
|
||||||
proxies[key.Name()] = adapter
|
|
||||||
case "select":
|
|
||||||
if len(rule) < 2 {
|
|
||||||
return fmt.Errorf("Selector need more than 2 param")
|
|
||||||
}
|
|
||||||
proxyNames := rule[1:]
|
|
||||||
selectProxy := make(map[string]C.Proxy)
|
|
||||||
for _, name := range proxyNames {
|
|
||||||
proxy, exist := proxies[name]
|
|
||||||
if !exist {
|
|
||||||
return fmt.Errorf("Proxy %s not exist", name)
|
|
||||||
}
|
|
||||||
selectProxy[name] = proxy
|
|
||||||
}
|
|
||||||
selector, err := adapters.NewSelector(key.Name(), selectProxy)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Selector create error: %s", err.Error())
|
|
||||||
}
|
|
||||||
proxies[key.Name()] = selector
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// init proxy
|
|
||||||
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", proxies)
|
|
||||||
proxies["DIRECT"] = adapters.NewDirect()
|
proxies["DIRECT"] = adapters.NewDirect()
|
||||||
proxies["REJECT"] = adapters.NewReject()
|
proxies["REJECT"] = adapters.NewReject()
|
||||||
|
|
||||||
|
// parse proxy
|
||||||
|
for idx, mapping := range proxiesConfig {
|
||||||
|
proxyType, existType := mapping["type"].(string)
|
||||||
|
proxyName, existName := mapping["name"].(string)
|
||||||
|
if !existType && existName {
|
||||||
|
return fmt.Errorf("Proxy %d missing type or name", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exist := proxies[proxyName]; exist {
|
||||||
|
return fmt.Errorf("Proxy %s is the duplicate name", proxyName)
|
||||||
|
}
|
||||||
|
var proxy C.Proxy
|
||||||
|
var err error
|
||||||
|
switch proxyType {
|
||||||
|
case "ss":
|
||||||
|
ssOption := &adapters.ShadowSocksOption{}
|
||||||
|
err = decoder.Decode(mapping, ssOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
proxy, err = adapters.NewShadowSocks(*ssOption)
|
||||||
|
case "socks5":
|
||||||
|
socksOption := &adapters.Socks5Option{}
|
||||||
|
err = decoder.Decode(mapping, socksOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
proxy = adapters.NewSocks5(*socksOption)
|
||||||
|
case "vmess":
|
||||||
|
vmessOption := &adapters.VmessOption{}
|
||||||
|
err = decoder.Decode(mapping, vmessOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
proxy, err = adapters.NewVmess(*vmessOption)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Proxy %s: %s", proxyName, err.Error())
|
||||||
|
}
|
||||||
|
proxies[proxyName] = proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse proxy group
|
||||||
|
for idx, mapping := range groupsConfig {
|
||||||
|
groupType, existType := mapping["type"].(string)
|
||||||
|
groupName, existName := mapping["name"].(string)
|
||||||
|
if !existType && existName {
|
||||||
|
return fmt.Errorf("ProxyGroup %d: missing type or name", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exist := proxies[groupName]; exist {
|
||||||
|
return fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
|
||||||
|
}
|
||||||
|
var group C.Proxy
|
||||||
|
var err error
|
||||||
|
switch groupType {
|
||||||
|
case "url-test":
|
||||||
|
urlTestOption := &adapters.URLTestOption{}
|
||||||
|
err = decoder.Decode(mapping, urlTestOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := getProxies(proxies, urlTestOption.Proxies)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
|
||||||
|
}
|
||||||
|
group, err = adapters.NewURLTest(*urlTestOption, ps)
|
||||||
|
case "select":
|
||||||
|
selectorOption := &adapters.SelectorOption{}
|
||||||
|
err = decoder.Decode(mapping, selectorOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := getProxies(proxies, selectorOption.Proxies)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
|
||||||
|
}
|
||||||
|
group, err = adapters.NewSelector(selectorOption.Name, ps)
|
||||||
|
case "fallback":
|
||||||
|
fallbackOption := &adapters.FallbackOption{}
|
||||||
|
err = decoder.Decode(mapping, fallbackOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := getProxies(proxies, fallbackOption.Proxies)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
|
||||||
|
}
|
||||||
|
group, err = adapters.NewFallback(*fallbackOption, ps)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Proxy %s: %s", groupName, err.Error())
|
||||||
|
}
|
||||||
|
proxies[groupName] = group
|
||||||
|
}
|
||||||
|
|
||||||
|
var ps []C.Proxy
|
||||||
|
for _, v := range proxies {
|
||||||
|
ps = append(ps, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps)
|
||||||
|
|
||||||
|
// close old goroutine
|
||||||
|
for _, proxy := range c.proxies {
|
||||||
|
switch raw := proxy.(type) {
|
||||||
|
case *adapters.URLTest:
|
||||||
|
raw.Close()
|
||||||
|
case *adapters.Fallback:
|
||||||
|
raw.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
c.proxies = proxies
|
c.proxies = proxies
|
||||||
c.event <- &Event{Type: "proxies", Payload: proxies}
|
c.event <- &Event{Type: "proxies", Payload: proxies}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) parseRules(cfg *ini.File) error {
|
func (c *Config) parseRules(cfg *RawConfig) error {
|
||||||
rules := []C.Rule{}
|
rules := []C.Rule{}
|
||||||
|
|
||||||
rulesConfig := cfg.Section("Rule")
|
rulesConfig := cfg.Rule
|
||||||
// parse rules
|
// parse rules
|
||||||
reader := bufio.NewReader(strings.NewReader(rulesConfig.Body()))
|
for _, line := range rulesConfig {
|
||||||
for {
|
rule := strings.Split(line, ",")
|
||||||
line, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
rule := strings.Split(string(line), ",")
|
|
||||||
if len(rule) < 3 {
|
if len(rule) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -55,16 +55,23 @@ func downloadMMDB(path string) (err error) {
|
|||||||
|
|
||||||
// Init prepare necessary files
|
// Init prepare necessary files
|
||||||
func Init() {
|
func Init() {
|
||||||
|
// initial homedir
|
||||||
|
if _, err := os.Stat(C.Path.HomeDir()); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(C.Path.HomeDir(), 0777); err != nil {
|
||||||
|
log.Fatalf("Can't create config directory %s: %s", C.Path.HomeDir(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// initial config.ini
|
// initial config.ini
|
||||||
if _, err := os.Stat(C.ConfigPath); 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 a empty file")
|
||||||
os.OpenFile(C.ConfigPath, os.O_CREATE|os.O_WRONLY, 0644)
|
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial mmdb
|
// initial mmdb
|
||||||
if _, err := os.Stat(C.MMDBPath); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||||
log.Info("Can't find MMDB, start download")
|
log.Info("Can't find MMDB, start download")
|
||||||
err := downloadMMDB(C.MMDBPath)
|
err := downloadMMDB(C.Path.MMDB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Can't download MMDB: %s", err.Error())
|
log.Fatalf("Can't download MMDB: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
func trimArr(arr []string) (r []string) {
|
func trimArr(arr []string) (r []string) {
|
||||||
@ -19,6 +21,18 @@ func genAddr(port int, allowLan bool) string {
|
|||||||
return fmt.Sprintf("127.0.0.1:%d", port)
|
return fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
|
||||||
|
var ps []C.Proxy
|
||||||
|
for _, name := range list {
|
||||||
|
p, ok := mapping[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("'%s' not found", name)
|
||||||
|
}
|
||||||
|
ps = append(ps, p)
|
||||||
|
}
|
||||||
|
return ps, nil
|
||||||
|
}
|
||||||
|
|
||||||
func or(pointers ...*int) *int {
|
func or(pointers ...*int) *int {
|
||||||
for _, p := range pointers {
|
for _, p := range pointers {
|
||||||
if p != nil {
|
if p != nil {
|
||||||
@ -27,20 +41,3 @@ func or(pointers ...*int) *int {
|
|||||||
}
|
}
|
||||||
return pointers[len(pointers)-1]
|
return pointers[len(pointers)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptions(startIdx int, params ...string) map[string]string {
|
|
||||||
mapping := make(map[string]string)
|
|
||||||
if len(params) <= startIdx {
|
|
||||||
return mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, option := range params[startIdx:] {
|
|
||||||
pair := strings.SplitN(option, "=", 2)
|
|
||||||
if len(pair) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping[strings.Trim(pair[0], " ")] = strings.Trim(pair[1], " ")
|
|
||||||
}
|
|
||||||
return mapping
|
|
||||||
}
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
// Adapter Type
|
// Adapter Type
|
||||||
const (
|
const (
|
||||||
Direct AdapterType = iota
|
Direct AdapterType = iota
|
||||||
|
Fallback
|
||||||
Reject
|
Reject
|
||||||
Selector
|
Selector
|
||||||
Shadowsocks
|
Shadowsocks
|
||||||
@ -21,14 +22,14 @@ type ProxyAdapter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerAdapter interface {
|
type ServerAdapter interface {
|
||||||
Addr() *Addr
|
Metadata() *Metadata
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
Generator(addr *Addr) (ProxyAdapter, error)
|
Generator(metadata *Metadata) (ProxyAdapter, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdapterType is enum of adapter type
|
// AdapterType is enum of adapter type
|
||||||
@ -38,6 +39,8 @@ func (at AdapterType) String() string {
|
|||||||
switch at {
|
switch at {
|
||||||
case Direct:
|
case Direct:
|
||||||
return "Direct"
|
return "Direct"
|
||||||
|
case Fallback:
|
||||||
|
return "Fallback"
|
||||||
case Reject:
|
case Reject:
|
||||||
return "Reject"
|
return "Reject"
|
||||||
case Selector:
|
case Selector:
|
||||||
|
@ -1,23 +1,5 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Name = "clash"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
HomeDir string
|
|
||||||
ConfigPath string
|
|
||||||
MMDBPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
type General struct {
|
type General struct {
|
||||||
Mode *string `json:"mode,omitempty"`
|
Mode *string `json:"mode,omitempty"`
|
||||||
AllowLan *bool `json:"allow-lan,omitempty"`
|
AllowLan *bool `json:"allow-lan,omitempty"`
|
||||||
@ -26,26 +8,3 @@ type General struct {
|
|||||||
RedirPort *int `json:"redir-port,omitempty"`
|
RedirPort *int `json:"redir-port,omitempty"`
|
||||||
LogLevel *string `json:"log-level,omitempty"`
|
LogLevel *string `json:"log-level,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
currentUser, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
dir := os.Getenv("HOME")
|
|
||||||
if dir == "" {
|
|
||||||
log.Fatalf("Can't get current user: %s", err.Error())
|
|
||||||
}
|
|
||||||
HomeDir = dir
|
|
||||||
} else {
|
|
||||||
HomeDir = currentUser.HomeDir
|
|
||||||
}
|
|
||||||
|
|
||||||
dirPath := path.Join(HomeDir, ".config", Name)
|
|
||||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(dirPath, 0777); err != nil {
|
|
||||||
log.Fatalf("Can't create config directory %s: %s", dirPath, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigPath = path.Join(dirPath, "config.ini")
|
|
||||||
MMDBPath = path.Join(dirPath, "Country.mmdb")
|
|
||||||
}
|
|
||||||
|
@ -28,8 +28,8 @@ func (n *NetWork) String() string {
|
|||||||
|
|
||||||
type SourceType int
|
type SourceType int
|
||||||
|
|
||||||
// Addr is used to store connection address
|
// Metadata is used to store connection address
|
||||||
type Addr struct {
|
type Metadata struct {
|
||||||
NetWork NetWork
|
NetWork NetWork
|
||||||
Source SourceType
|
Source SourceType
|
||||||
AddrType int
|
AddrType int
|
||||||
@ -38,7 +38,7 @@ type Addr struct {
|
|||||||
Port string
|
Port string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (addr *Addr) String() string {
|
func (addr *Metadata) String() string {
|
||||||
if addr.Host == "" {
|
if addr.Host == "" {
|
||||||
return addr.IP.String()
|
return addr.IP.String()
|
||||||
}
|
}
|
49
constant/path.go
Normal file
49
constant/path.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
P "path"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = "clash"
|
||||||
|
|
||||||
|
// Path is used to get the configuration path
|
||||||
|
var Path *path
|
||||||
|
|
||||||
|
type path struct {
|
||||||
|
homedir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
var homedir string
|
||||||
|
if err != nil {
|
||||||
|
dir := os.Getenv("HOME")
|
||||||
|
if dir == "" {
|
||||||
|
dir, _ = os.Getwd()
|
||||||
|
}
|
||||||
|
homedir = dir
|
||||||
|
} else {
|
||||||
|
homedir = currentUser.HomeDir
|
||||||
|
}
|
||||||
|
homedir = P.Join(homedir, ".config", Name)
|
||||||
|
Path = &path{homedir: homedir}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHomeDir is used to set the configuration path
|
||||||
|
func SetHomeDir(root string) {
|
||||||
|
Path = &path{homedir: root}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) HomeDir() string {
|
||||||
|
return p.homedir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) Config() string {
|
||||||
|
return P.Join(p.homedir, "config.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) MMDB() string {
|
||||||
|
return P.Join(p.homedir, "Country.mmdb")
|
||||||
|
}
|
@ -33,7 +33,7 @@ func (rt RuleType) String() string {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
RuleType() RuleType
|
RuleType() RuleType
|
||||||
IsMatch(addr *Addr) bool
|
IsMatch(metadata *Metadata) bool
|
||||||
Adapter() string
|
Adapter() string
|
||||||
Payload() string
|
Payload() string
|
||||||
}
|
}
|
||||||
|
16
go.mod
Normal file
16
go.mod
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module github.com/Dreamacro/clash
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270
|
||||||
|
github.com/eapache/queue v1.1.0 // indirect
|
||||||
|
github.com/go-chi/chi v3.3.3+incompatible
|
||||||
|
github.com/go-chi/cors v1.0.0
|
||||||
|
github.com/go-chi/render v1.0.1
|
||||||
|
github.com/gofrs/uuid v3.1.0+incompatible
|
||||||
|
github.com/oschwald/geoip2-golang v1.2.1
|
||||||
|
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.1.0
|
||||||
|
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941
|
||||||
|
gopkg.in/eapache/channels.v1 v1.1.0
|
||||||
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
|
)
|
41
go.sum
Normal file
41
go.sum
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 h1:ugkI+Yw5ArFnhF8KTbJxyWIyvxMCa8jWyUF+wIAulhM=
|
||||||
|
github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps=
|
||||||
|
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/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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
|
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 v3.3.3+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/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/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
|
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
||||||
|
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
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/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
|
||||||
|
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU=
|
||||||
|
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
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-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
|
||||||
|
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||||
|
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-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
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/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||||
|
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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
A "github.com/Dreamacro/clash/adapters/remote"
|
A "github.com/Dreamacro/clash/adapters/outbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
@ -37,6 +37,11 @@ type URLTest struct {
|
|||||||
Now string `json:"now"`
|
Now string `json:"now"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Fallback struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Now string `json:"now"`
|
||||||
|
}
|
||||||
|
|
||||||
func transformProxy(proxy C.Proxy) interface{} {
|
func transformProxy(proxy C.Proxy) interface{} {
|
||||||
t := proxy.Type()
|
t := proxy.Type()
|
||||||
switch t {
|
switch t {
|
||||||
@ -52,6 +57,11 @@ func transformProxy(proxy C.Proxy) interface{} {
|
|||||||
Type: t.String(),
|
Type: t.String(),
|
||||||
Now: proxy.(*A.URLTest).Now(),
|
Now: proxy.(*A.URLTest).Now(),
|
||||||
}
|
}
|
||||||
|
case C.Fallback:
|
||||||
|
return Fallback{
|
||||||
|
Type: t.String(),
|
||||||
|
Now: proxy.(*A.Fallback).Now(),
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return SampleProxy{
|
return SampleProxy{
|
||||||
Type: proxy.Type().String(),
|
Type: proxy.Type().String(),
|
||||||
|
@ -17,6 +17,7 @@ func ruleRouter() http.Handler {
|
|||||||
type Rule struct {
|
type Rule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Payload string `json:"type"`
|
Payload string `json:"type"`
|
||||||
|
Proxy string `json:"proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetRulesResponse struct {
|
type GetRulesResponse struct {
|
||||||
@ -31,6 +32,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
|
|||||||
rules = append(rules, Rule{
|
rules = append(rules, Rule{
|
||||||
Name: rule.RuleType().String(),
|
Name: rule.RuleType().String(),
|
||||||
Payload: rule.Payload(),
|
Payload: rule.Payload(),
|
||||||
|
Proxy: rule.Adapter(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package hub
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
@ -15,6 +16,8 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var secret = ""
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
Up int64 `json:"up"`
|
Up int64 `json:"up"`
|
||||||
Down int64 `json:"down"`
|
Down int64 `json:"down"`
|
||||||
@ -24,11 +27,19 @@ func newHub(signal chan struct{}) {
|
|||||||
var addr string
|
var addr string
|
||||||
ch := config.Instance().Subscribe()
|
ch := config.Instance().Subscribe()
|
||||||
signal <- struct{}{}
|
signal <- struct{}{}
|
||||||
|
count := 0
|
||||||
for {
|
for {
|
||||||
elm := <-ch
|
elm := <-ch
|
||||||
event := elm.(*config.Event)
|
event := elm.(*config.Event)
|
||||||
if event.Type == "external-controller" {
|
switch event.Type {
|
||||||
|
case "external-controller":
|
||||||
addr = event.Payload.(string)
|
addr = event.Payload.(string)
|
||||||
|
count++
|
||||||
|
case "secret":
|
||||||
|
secret = event.Payload.(string)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if count == 2 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,11 +49,11 @@ func newHub(signal chan struct{}) {
|
|||||||
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", "DELETE", "OPTIONS"},
|
||||||
AllowedHeaders: []string{"Content-Type"},
|
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
||||||
MaxAge: 300,
|
MaxAge: 300,
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Use(cors.Handler)
|
r.Use(cors.Handler, authentication)
|
||||||
|
|
||||||
r.With(jsonContentType).Get("/traffic", traffic)
|
r.With(jsonContentType).Get("/traffic", traffic)
|
||||||
r.With(jsonContentType).Get("/logs", getLogs)
|
r.With(jsonContentType).Get("/logs", getLogs)
|
||||||
@ -65,6 +76,30 @@ func jsonContentType(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authentication(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
header := r.Header.Get("Authorization")
|
||||||
|
text := strings.SplitN(header, " ", 2)
|
||||||
|
|
||||||
|
if secret == "" {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUnvalidHeader := text[0] != "Bearer"
|
||||||
|
hasUnvalidSecret := len(text) == 2 && text[1] != secret
|
||||||
|
if hasUnvalidHeader || hasUnvalidSecret {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
render.JSON(w, r, Error{
|
||||||
|
Error: "Authentication failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
|
||||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
20
main.go
20
main.go
@ -4,20 +4,40 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"flag"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
"github.com/Dreamacro/clash/hub"
|
"github.com/Dreamacro/clash/hub"
|
||||||
"github.com/Dreamacro/clash/proxy"
|
"github.com/Dreamacro/clash/proxy"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
homedir string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&homedir, "d", "", "set configuration directory")
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
tunnel.Instance().Run()
|
tunnel.Instance().Run()
|
||||||
proxy.Instance().Run()
|
proxy.Instance().Run()
|
||||||
hub.Run()
|
hub.Run()
|
||||||
|
|
||||||
|
if (homedir != "") {
|
||||||
|
if !path.IsAbs(homedir) {
|
||||||
|
currentDir, _ := os.Getwd()
|
||||||
|
homedir = path.Join(currentDir, homedir)
|
||||||
|
}
|
||||||
|
C.SetHomeDir(homedir)
|
||||||
|
}
|
||||||
|
|
||||||
config.Init()
|
config.Init()
|
||||||
err := config.Instance().Parse()
|
err := config.Instance().Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/local"
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package redir
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/local"
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parserPacket(c net.Conn) (socks.Addr, error) {
|
func parserPacket(c net.Conn) (socks.Addr, error) {
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parserPacket(conn net.Conn) (socks.Addr, error) {
|
func parserPacket(conn net.Conn) (socks.Addr, error) {
|
||||||
|
@ -3,11 +3,11 @@ package socks
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/local"
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/riobard/go-shadowsocks2/socks"
|
"github.com/Dreamacro/go-shadowsocks2/socks"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ func (d *Domain) RuleType() C.RuleType {
|
|||||||
return C.Domain
|
return C.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Domain) IsMatch(addr *C.Addr) bool {
|
func (d *Domain) IsMatch(metadata *C.Metadata) bool {
|
||||||
if addr.AddrType != C.AtypDomainName {
|
if metadata.AddrType != C.AtypDomainName {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return addr.Host == d.domain
|
return metadata.Host == d.domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Domain) Adapter() string {
|
func (d *Domain) Adapter() string {
|
@ -15,11 +15,11 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
|
|||||||
return C.DomainKeyword
|
return C.DomainKeyword
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dk *DomainKeyword) IsMatch(addr *C.Addr) bool {
|
func (dk *DomainKeyword) IsMatch(metadata *C.Metadata) bool {
|
||||||
if addr.AddrType != C.AtypDomainName {
|
if metadata.AddrType != C.AtypDomainName {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
domain := addr.Host
|
domain := metadata.Host
|
||||||
return strings.Contains(domain, dk.keyword)
|
return strings.Contains(domain, dk.keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ func (ds *DomainSuffix) RuleType() C.RuleType {
|
|||||||
return C.DomainSuffix
|
return C.DomainSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DomainSuffix) IsMatch(addr *C.Addr) bool {
|
func (ds *DomainSuffix) IsMatch(metadata *C.Metadata) bool {
|
||||||
if addr.AddrType != C.AtypDomainName {
|
if metadata.AddrType != C.AtypDomainName {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
domain := addr.Host
|
domain := metadata.Host
|
||||||
return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix
|
return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ func (f *Final) RuleType() C.RuleType {
|
|||||||
return C.FINAL
|
return C.FINAL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Final) IsMatch(addr *C.Addr) bool {
|
func (f *Final) IsMatch(metadata *C.Metadata) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ func (g *GEOIP) RuleType() C.RuleType {
|
|||||||
return C.GEOIP
|
return C.GEOIP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GEOIP) IsMatch(addr *C.Addr) bool {
|
func (g *GEOIP) IsMatch(metadata *C.Metadata) bool {
|
||||||
if addr.IP == nil {
|
if metadata.IP == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
record, _ := mmdb.Country(*addr.IP)
|
record, _ := mmdb.Country(*metadata.IP)
|
||||||
return record.Country.IsoCode == g.country
|
return record.Country.IsoCode == g.country
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ func (g *GEOIP) Payload() string {
|
|||||||
func NewGEOIP(country string, adapter string) *GEOIP {
|
func NewGEOIP(country string, adapter string) *GEOIP {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
var err error
|
var err error
|
||||||
mmdb, err = geoip2.Open(C.MMDBPath)
|
mmdb, err = geoip2.Open(C.Path.MMDB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Can't load mmdb: %s", err.Error())
|
log.Fatalf("Can't load mmdb: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,12 @@ func (i *IPCIDR) RuleType() C.RuleType {
|
|||||||
return C.IPCIDR
|
return C.IPCIDR
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IPCIDR) IsMatch(addr *C.Addr) bool {
|
func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool {
|
||||||
if addr.IP == nil {
|
if metadata.IP == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return i.ipnet.Contains(*addr.IP)
|
return i.ipnet.Contains(*metadata.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IPCIDR) Adapter() string {
|
func (i *IPCIDR) Adapter() string {
|
||||||
|
@ -5,8 +5,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/local"
|
"github.com/Dreamacro/clash/adapters/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,9 +67,11 @@ func relay(leftConn, rightConn net.Conn) {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, err := io.Copy(leftConn, rightConn)
|
_, err := io.Copy(leftConn, rightConn)
|
||||||
|
leftConn.SetReadDeadline(time.Now())
|
||||||
ch <- err
|
ch <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
io.Copy(rightConn, leftConn)
|
io.Copy(rightConn, leftConn)
|
||||||
|
rightConn.SetReadDeadline(time.Now())
|
||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
LocalAdapter "github.com/Dreamacro/clash/adapters/local"
|
InboundAdapter "github.com/Dreamacro/clash/adapters/inbound"
|
||||||
|
"github.com/Dreamacro/clash/common/observable"
|
||||||
cfg "github.com/Dreamacro/clash/config"
|
cfg "github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/observable"
|
|
||||||
|
|
||||||
"gopkg.in/eapache/channels.v1"
|
"gopkg.in/eapache/channels.v1"
|
||||||
)
|
)
|
||||||
@ -84,7 +84,7 @@ func (t *Tunnel) process() {
|
|||||||
|
|
||||||
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||||
defer localConn.Close()
|
defer localConn.Close()
|
||||||
addr := localConn.Addr()
|
metadata := localConn.Metadata()
|
||||||
|
|
||||||
var proxy C.Proxy
|
var proxy C.Proxy
|
||||||
switch t.mode {
|
switch t.mode {
|
||||||
@ -94,9 +94,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||||||
proxy = t.proxies["GLOBAL"]
|
proxy = t.proxies["GLOBAL"]
|
||||||
// Rule
|
// Rule
|
||||||
default:
|
default:
|
||||||
proxy = t.match(addr)
|
proxy = t.match(metadata)
|
||||||
}
|
}
|
||||||
remoConn, err := proxy.Generator(addr)
|
remoConn, err := proxy.Generator(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error())
|
t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error())
|
||||||
return
|
return
|
||||||
@ -104,28 +104,28 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||||||
defer remoConn.Close()
|
defer remoConn.Close()
|
||||||
|
|
||||||
switch adapter := localConn.(type) {
|
switch adapter := localConn.(type) {
|
||||||
case *LocalAdapter.HTTPAdapter:
|
case *InboundAdapter.HTTPAdapter:
|
||||||
t.handleHTTP(adapter, remoConn)
|
t.handleHTTP(adapter, remoConn)
|
||||||
case *LocalAdapter.SocketAdapter:
|
case *InboundAdapter.SocketAdapter:
|
||||||
t.handleSOCKS(adapter, remoConn)
|
t.handleSOCKS(adapter, remoConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) match(addr *C.Addr) C.Proxy {
|
func (t *Tunnel) match(metadata *C.Metadata) C.Proxy {
|
||||||
t.configLock.RLock()
|
t.configLock.RLock()
|
||||||
defer t.configLock.RUnlock()
|
defer t.configLock.RUnlock()
|
||||||
|
|
||||||
for _, rule := range t.rules {
|
for _, rule := range t.rules {
|
||||||
if rule.IsMatch(addr) {
|
if rule.IsMatch(metadata) {
|
||||||
a, ok := t.proxies[rule.Adapter()]
|
a, ok := t.proxies[rule.Adapter()]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.logCh <- newLog(C.INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter())
|
t.logCh <- newLog(C.INFO, "%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter())
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", addr.String())
|
t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", metadata.String())
|
||||||
return t.proxies["DIRECT"]
|
return t.proxies["DIRECT"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user