Compare commits

...

52 Commits

Author SHA1 Message Date
646bd4eeb4 Chore: update dependencies and README.md 2020-05-07 21:58:53 +08:00
752f87a8dc Feature: support proxy-group in relay (#597) 2020-05-07 21:42:52 +08:00
b979ff0bc2 Feature: implemented a strategy similar to optimistic DNS (#647) 2020-05-07 15:10:14 +08:00
b085addbb0 Fix: use domain first on direct dial (#672) 2020-05-05 12:39:25 +08:00
94e0e4b000 Fix: make selector react immediately 2020-04-30 20:13:27 +08:00
7d51ab5846 Fix: dns return empty success for AAAA & recursion in fake ip mode (#663) 2020-04-29 11:21:37 +08:00
41a9488cfa Feature: add more command-line options (#656)
add command-line options to override `external-controller`, `secret` and `external-ui` (#531)
2020-04-27 22:23:09 +08:00
51b6b8521b Fix: typo (#657) 2020-04-27 22:20:35 +08:00
e5379558f6 Fix: redir-host should lookup hosts 2020-04-27 21:28:24 +08:00
d1fd57c432 Fix: select group can use provider real-time 2020-04-27 21:23:23 +08:00
18603c9a46 Improve: provider can be auto GC 2020-04-26 22:38:15 +08:00
5036f62a9c Chore: update dependencies 2020-04-25 00:43:32 +08:00
2047b8eda1 Chore: remove unused parameter netType (#651) 2020-04-25 00:39:30 +08:00
0e56c195bb Improve: pool buffer alloc 2020-04-25 00:30:40 +08:00
2b33bfae6b Fix: API auth bypass 2020-04-24 23:49:35 +08:00
3fc6d55003 Fix: domain wildcard behavior 2020-04-24 23:49:19 +08:00
8eddcd77bf Chore: dialer hook should return a error 2020-04-24 23:48:55 +08:00
27dd1d7944 Improve: add basic auth support for provider URL (#645) 2020-04-20 21:22:23 +08:00
b1cf2ec837 Fix: dns tcp-tls X509.HostnameError (#638) 2020-04-17 11:29:59 +08:00
84f627f302 Feature: verify mmdb on initial 2020-04-16 19:12:25 +08:00
5c03613858 Chore: picker support get first error 2020-04-16 18:31:40 +08:00
1825535abd Improve: recycle buffer after packet used 2020-04-16 18:19:36 +08:00
2750c7ead0 Fix: set SO_REUSEADDR for UDP listeners on linux (#630) 2020-04-11 21:45:56 +08:00
3ccd7def86 Fix: typo (#624) 2020-04-08 15:49:12 +08:00
65dab4e34f Feature: domain trie support dot dot wildcard 2020-04-08 15:45:59 +08:00
5591e15452 Fix: vmess pure TLS mode 2020-04-03 16:04:24 +08:00
19f809b1c8 Feature: refactor vmess & add http network 2020-03-31 16:07:21 +08:00
206767247e Fix: udp traffic track (#608) 2020-03-28 20:05:38 +08:00
518354e7eb Fix: dns request panic and close #527 2020-03-24 10:13:53 +08:00
86dfb6562c Chore: update dependencies 2020-03-22 17:41:58 +08:00
c0a2473160 Feature: support relay (proxy chains) (#539) 2020-03-21 23:46:49 +08:00
70a19b999d Chore: update README to better describe what Clash do atm (#586) 2020-03-20 12:35:30 +08:00
e54f51af81 Fix: trojan split udp packet 2020-03-20 00:02:05 +08:00
b068466108 Improve: add session cache for trojan 2020-03-19 22:39:09 +08:00
b562f28c1b Feature: support trojan 2020-03-19 20:26:53 +08:00
230e01f078 Fix: config parse panic 2020-03-19 11:04:56 +08:00
082847b403 Chore: support MarshalYAML to some config filed (#581) 2020-03-15 19:40:39 +08:00
9471d80785 Fix: dns fallback logic 2020-03-13 00:11:54 +08:00
b263095533 Fix: TPROXY fakeip (#572) 2020-03-10 20:36:24 +08:00
Ti
14d5137703 Fix: rules parse (#568) 2020-03-09 10:40:21 +08:00
d8a771916a Fix: provider parse 2020-03-08 23:34:46 +08:00
f7f30d3406 Feature: add UDP TPROXY support on Linux (#562) 2020-03-08 21:58:49 +08:00
b2c9cbb43e Chore: update dependencies 2020-03-08 13:01:06 +08:00
c733f80793 Fix: #563 and fallback error return 2020-03-08 13:00:42 +08:00
88d8f93793 Change: rename some field 2020-03-07 20:01:24 +08:00
e57a13ed7a Fix: mutable SplitAddr cause panic 2020-03-02 23:47:23 +08:00
23525ecc15 Migration: go 1.14 2020-03-01 01:48:08 +08:00
814bd05315 Fix: ss udp return error when addr parse failed 2020-03-01 01:46:02 +08:00
e81b88fb94 Feature: add configuration test command (#524) 2020-02-29 17:48:26 +08:00
c4994d6429 Fix: dns not cache RcodeServerFailure 2020-02-25 21:53:28 +08:00
0740d20ba0 Chore: disable url-test http redirect (#536) 2020-02-25 16:08:13 +08:00
9eaca6e4ab Fix: provider fallback should reparse proxies 2020-02-22 15:18:03 +08:00
82 changed files with 2373 additions and 719 deletions

View File

@ -9,7 +9,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v1 uses: actions/setup-go@v1
with: with:
go-version: 1.13.x go-version: 1.14.x
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v1 uses: actions/checkout@v1

108
README.md
View File

@ -19,23 +19,25 @@
## Features ## Features
- Local HTTP/HTTPS/SOCKS server - Local HTTP/HTTPS/SOCKS server with/without authentication
- GeoIP rule support - VMess, Shadowsocks, Trojan (experimental), Snell protocol support for remote connections. UDP is supported.
- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol - Built-in DNS server that aims to minimize DNS pollution attacks, supports DoH/DoT upstream. Fake IP is also supported.
- Supports Netfilter TCP redirecting - Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes
- Comprehensive HTTP API - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
- Netfilter TCP redirecting. You can deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP API controller
## Install ## Install
Clash Requires Go >= 1.13. You can build it from source: Clash requires Go >= 1.13. You can build it from source:
```sh ```sh
$ go get -u -v github.com/Dreamacro/clash $ go get -u -v github.com/Dreamacro/clash
``` ```
Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases)
Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available.
Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN)
Check Clash version with: Check Clash version with:
@ -43,21 +45,17 @@ Check Clash version with:
$ clash -v $ clash -v
``` ```
## Daemon ## Daemonize Clash
Unfortunately, there is no native and elegant way to implement daemons on Golang. Unfortunately, there is no native or elegant way to implement daemons on Golang. We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service.
So we can use third-party daemon tools like PM2, Supervisor or the like. In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way:
In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way:
```sh ```sh
$ pm2 start clash $ pm2 start clash
``` ```
If you have Docker installed, you can run clash directly using `docker-compose`. If you have Docker installed, it's recommended to deploy Clash directly using `docker-compose`: [run Clash in Docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
## Config ## Config
@ -122,9 +120,10 @@ experimental:
# - "user2:pass2" # - "user2:pass2"
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) # # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com) # # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com)
# hosts: # hosts:
# '*.clash.dev': 127.0.0.1 # '*.clash.dev': 127.0.0.1
# '.dev': 127.0.0.1
# 'alpha.clash.dev': '::1' # 'alpha.clash.dev': '::1'
# dns: # dns:
@ -150,7 +149,7 @@ experimental:
# ipcidr: # ips in these subnets will be considered polluted # ipcidr: # ips in these subnets will be considered polluted
# - 240.0.0.0/4 # - 240.0.0.0/4
Proxy: proxies:
# shadowsocks # shadowsocks
# The supported ciphers(encrypt methods): # The supported ciphers(encrypt methods):
# aes-128-gcm aes-192-gcm aes-256-gcm # aes-128-gcm aes-192-gcm aes-256-gcm
@ -212,6 +211,24 @@ Proxy:
# ws-headers: # ws-headers:
# Host: v2ray.com # Host: v2ray.com
- name: "vmess-http"
type: vmess
server: server
port: 443
uuid: uuid
alterId: 32
cipher: auto
# udp: true
# network: http
# http-opts:
# # method: "GET"
# # path:
# # - '/'
# # - '/video'
# # headers:
# # Connection:
# # - keep-alive
# socks5 # socks5
- name: "socks" - name: "socks"
type: socks5 type: socks5
@ -243,7 +260,30 @@ Proxy:
# mode: http # or tls # mode: http # or tls
# host: bing.com # host: bing.com
Proxy Group: # trojan
- name: "trojan"
type: trojan
server: server
port: 443
password: yourpsk
# udp: true
# sni: example.com # aka server name
# alpn:
# - h2
# - http/1.1
# skip-cert-verify: true
proxy-groups:
# relay chains the proxies. proxies shall not contain a relay. No UDP support.
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
- name: "relay"
type: relay
proxies:
- http
- vmess
- ss1
- ss2
# url-test select which proxy will be used by benchmarking speed to a URL. # url-test select which proxy will be used by benchmarking speed to a URL.
- name: "auto" - name: "auto"
type: url-test type: url-test
@ -284,7 +324,33 @@ Proxy Group:
- vmess1 - vmess1
- auto - auto
Rule: - name: UseProvider
type: select
use:
- provider1
proxies:
- Proxy
- DIRECT
proxy-providers:
provider1:
type: http
url: "url"
interval: 3600
path: ./hk.yaml
health-check:
enable: true
interval: 600
url: http://www.gstatic.com/generate_204
test:
type: file
path: /test.yaml
health-check:
enable: true
interval: 36000
url: http://www.gstatic.com/generate_204
rules:
- DOMAIN-SUFFIX,google.com,auto - DOMAIN-SUFFIX,google.com,auto
- DOMAIN-KEYWORD,google,auto - DOMAIN-KEYWORD,google,auto
- DOMAIN,google.com,auto - DOMAIN,google.com,auto
@ -308,7 +374,7 @@ Rule:
## Documentations ## Documentations
https://clash.gitbook.io/ https://clash.gitbook.io/
## Thanks ## Credits
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)

View File

@ -19,9 +19,9 @@ func (s *SocketAdapter) Metadata() *C.Metadata {
} }
// NewSocket is SocketAdapter generator // NewSocket is SocketAdapter generator
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, netType C.NetWork) *SocketAdapter { func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = netType metadata.NetWork = C.TCP
metadata.Type = source metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip

View File

@ -18,6 +18,7 @@ var (
type Base struct { type Base struct {
name string name string
addr string
tp C.AdapterType tp C.AdapterType
udp bool udp bool
} }
@ -30,6 +31,10 @@ func (b *Base) Type() C.AdapterType {
return b.tp return b.tp
} }
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("no support") return nil, errors.New("no support")
} }
@ -44,8 +49,16 @@ func (b *Base) MarshalJSON() ([]byte, error) {
}) })
} }
func NewBase(name string, tp C.AdapterType, udp bool) *Base { func (b *Base) Addr() string {
return &Base{name, tp, udp} return b.addr
}
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
return nil
}
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
return &Base{name, addr, tp, udp}
} }
type conn struct { type conn struct {
@ -61,7 +74,7 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name()) c.chain = append(c.chain, a.Name())
} }
func newConn(c net.Conn, a C.ProxyAdapter) C.Conn { func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}} return &conn{c, []string{a.Name()}}
} }
@ -194,7 +207,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
} }
client := http.Client{Transport: transport} client := http.Client{
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return return

View File

@ -14,17 +14,14 @@ type Direct struct {
} }
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
address := net.JoinHostPort(metadata.Host, metadata.DstPort) address := net.JoinHostPort(metadata.String(), metadata.DstPort)
if metadata.DstIP != nil {
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
}
c, err := dialer.DialContext(ctx, "tcp", address) c, err := dialer.DialContext(ctx, "tcp", address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tcpKeepAlive(c) tcpKeepAlive(c)
return newConn(c, d), nil return NewConn(c, d), nil
} }
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {

View File

@ -19,7 +19,6 @@ import (
type Http struct { type Http struct {
*Base *Base
addr string
user string user string
pass string pass string
tlsConfig *tls.Config tlsConfig *tls.Config
@ -35,23 +34,35 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr) if h.tlsConfig != nil {
if err == nil && h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig) cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake() err := cc.Handshake()
c = cc c = cc
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
} }
if err := h.shakeHand(metadata, c); err != nil {
return nil, err
}
return c, nil
}
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
if err := h.shakeHand(metadata, c); err != nil {
c, err = h.StreamConn(c, metadata)
if err != nil {
return nil, err return nil, err
} }
return newConn(c, h), nil return NewConn(c, h), nil
} }
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
@ -113,9 +124,9 @@ func NewHttp(option HttpOption) *Http {
return &Http{ return &Http{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http, tp: C.Http,
}, },
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,

View File

@ -39,7 +39,12 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
} }
proxy = NewHttp(*httpOption) proxy = NewHttp(*httpOption)
case "vmess": case "vmess":
vmessOption := &VmessOption{} vmessOption := &VmessOption{
HTTPOpts: HTTPOptions{
Method: "GET",
Path: []string{"/"},
},
}
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
if err != nil { if err != nil {
break break
@ -52,6 +57,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
break break
} }
proxy, err = NewSnell(*snellOption) proxy, err = NewSnell(*snellOption)
case "trojan":
trojanOption := &TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
}
proxy, err = NewTrojan(*trojanOption)
default: default:
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
} }

View File

@ -15,7 +15,7 @@ type Reject struct {
} }
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return newConn(&NopConn{}, r), nil return NewConn(&NopConn{}, r), nil
} }
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {

View File

@ -2,8 +2,8 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -20,7 +20,6 @@ import (
type ShadowSocks struct { type ShadowSocks struct {
*Base *Base
server string
cipher core.Cipher cipher core.Cipher
// obfs // obfs
@ -59,28 +58,34 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
} }
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", ss.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
tcpKeepAlive(c)
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host) c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http": case "http":
_, port, _ := net.SplitHostPort(ss.server) _, port, _ := net.SplitHostPort(ss.addr)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket": case "websocket":
var err error var err error
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption) c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
} }
c = ss.cipher.StreamConn(c) c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata)) _, err := c.Write(serializesSocksAddr(metadata))
return newConn(c, ss), err return c, err
}
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
c, err = ss.StreamConn(c, metadata)
return NewConn(c, ss), err
} }
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
@ -89,7 +94,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ss.server) addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -105,12 +110,12 @@ func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
} }
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
password := option.Password password := option.Password
ciph, err := core.PickCipher(cipher, nil, password) ciph, err := core.PickCipher(cipher, nil, password)
if err != nil { if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", server, err) return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
} }
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
@ -132,49 +137,45 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if option.Plugin == "obfs" { if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"} opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil { if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err) return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err)
} }
if opts.Mode != "tls" && opts.Mode != "http" { if opts.Mode != "tls" && opts.Mode != "http" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode) return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
} }
obfsMode = opts.Mode obfsMode = opts.Mode
obfsOption = &opts obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" { } else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com", Mux: true} opts := v2rayObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil { if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err) return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
} }
if opts.Mode != "websocket" { if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode) return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
} }
obfsMode = opts.Mode obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
v2rayOption = &v2rayObfs.Option{ v2rayOption = &v2rayObfs.Option{
Host: opts.Host, Host: opts.Host,
Path: opts.Path, Path: opts.Path,
Headers: opts.Headers, Headers: opts.Headers,
TLSConfig: tlsConfig,
Mux: opts.Mux, Mux: opts.Mux,
} }
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.SessionCache = getClientSessionCache()
}
} }
return &ShadowSocks{ return &ShadowSocks{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr,
tp: C.Shadowsocks, tp: C.Shadowsocks,
udp: option.UDP, udp: option.UDP,
}, },
server: server,
cipher: ciph, cipher: ciph,
obfsMode: obfsMode, obfsMode: obfsMode,
@ -209,7 +210,17 @@ func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
if e != nil { if e != nil {
return 0, nil, e return 0, nil, e
} }
addr := socks5.SplitAddr(b[:n]) addr := socks5.SplitAddr(b[:n])
copy(b, b[len(addr):]) if addr == nil {
return n - len(addr), addr.UDPAddr(), e return 0, nil, errors.New("parse addr error")
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse addr error")
}
copy(b, b[len(addr):])
return n - len(addr), udpAddr, e
} }

View File

@ -15,7 +15,6 @@ import (
type Snell struct { type Snell struct {
*Base *Base
server string
psk []byte psk []byte
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
} }
@ -28,45 +27,51 @@ type SnellOption struct {
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
} }
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", s.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
}
tcpKeepAlive(c)
switch s.obfsOption.Mode { switch s.obfsOption.Mode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, s.obfsOption.Host) c = obfs.NewTLSObfs(c, s.obfsOption.Host)
case "http": case "http":
_, port, _ := net.SplitHostPort(s.server) _, port, _ := net.SplitHostPort(s.addr)
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
} }
c = snell.StreamConn(c, s.psk) c = snell.StreamConn(c, s.psk)
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.Atoi(metadata.DstPort)
err = snell.WriteHeader(c, metadata.String(), uint(port)) err := snell.WriteHeader(c, metadata.String(), uint(port))
return newConn(c, s), err return c, err
}
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
tcpKeepAlive(c)
c, err = s.StreamConn(c, metadata)
return NewConn(c, s), err
} }
func NewSnell(option SnellOption) (*Snell, error) { func NewSnell(option SnellOption) (*Snell, error) {
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk) psk := []byte(option.Psk)
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
obfsOption := &simpleObfsOption{Host: "bing.com"} obfsOption := &simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err) return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
} }
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode) return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
} }
return &Snell{ return &Snell{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr,
tp: C.Snell, tp: C.Snell,
}, },
server: server,
psk: psk, psk: psk,
obfsOption: obfsOption, obfsOption: obfsOption,
}, nil }, nil

View File

@ -3,6 +3,7 @@ package outbound
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -16,7 +17,6 @@ import (
type Socks5 struct { type Socks5 struct {
*Base *Base
addr string
user string user string
pass string pass string
tls bool tls bool
@ -35,19 +35,16 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr) if ss.tls {
if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig) cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake() err := cc.Handshake()
c = cc c = cc
}
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
tcpKeepAlive(c) }
var user *socks5.User var user *socks5.User
if ss.user != "" { if ss.user != "" {
user = &socks5.User{ user = &socks5.User{
@ -58,7 +55,22 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err return nil, err
} }
return newConn(c, ss), nil return c, nil
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
c, err = ss.StreamConn(c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, ss), nil
} }
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
@ -126,10 +138,10 @@ func NewSocks5(option Socks5Option) *Socks5 {
return &Socks5{ return &Socks5{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5, tp: C.Socks5,
udp: option.UDP, udp: option.UDP,
}, },
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tls: option.TLS, tls: option.TLS,
@ -161,7 +173,7 @@ func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n
} }
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, a, e := uc.PacketConn.ReadFrom(b) n, _, e := uc.PacketConn.ReadFrom(b)
if e != nil { if e != nil {
return 0, nil, e return 0, nil, e
} }
@ -169,10 +181,15 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse udp addr error")
}
// due to DecodeUDPPacket is mutable, record addr length // due to DecodeUDPPacket is mutable, record addr length
addrLength := len(addr)
copy(b, payload) copy(b, payload)
return n - addrLength - 3, a, nil return n - len(addr) - 3, udpAddr, nil
} }
func (uc *socksPacketConn) Close() error { func (uc *socksPacketConn) Close() error {

116
adapters/outbound/trojan.go Normal file
View File

@ -0,0 +1,116 @@
package outbound
import (
"context"
"encoding/json"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/trojan"
C "github.com/Dreamacro/clash/constant"
)
type Trojan struct {
*Base
instance *trojan.Trojan
}
type TrojanOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
UDP bool `proxy:"udp,omitempty"`
}
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err := t.instance.StreamConn(c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err
}
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
c, err = t.StreamConn(c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, t), err
}
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
c, err = t.instance.StreamConn(c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
return newPacketConn(&trojanPacketConn{pc, c}, t), err
}
func (t *Trojan) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": t.Type().String(),
})
}
func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{
Password: option.Password,
ALPN: option.ALPN,
ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
if option.SNI != "" {
tOption.ServerName = option.SNI
}
return &Trojan{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Trojan,
udp: option.UDP,
},
instance: trojan.New(tOption),
}, nil
}
type trojanPacketConn struct {
net.PacketConn
conn net.Conn
}
func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p)
}

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"strconv" "strconv"
"strings" "strings"
@ -16,8 +17,8 @@ import (
type Vmess struct { type Vmess struct {
*Base *Base
server string
client *vmess.Client client *vmess.Client
option *VmessOption
} }
type VmessOption struct { type VmessOption struct {
@ -30,19 +31,82 @@ type VmessOption struct {
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { type HTTPOptions struct {
c, err := dialer.DialContext(ctx, "tcp", v.server) Method string `proxy:"method,omitempty"`
Path []string `proxy:"path,omitempty"`
Headers map[string][]string `proxy:"headers,omitempty"`
}
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSPath,
}
if len(v.option.WSHeaders) != 0 {
header := http.Header{}
for key, value := range v.option.WSHeaders {
header.Add(key, value)
}
wsOpts.Headers = header
}
if v.option.TLS {
wsOpts.TLS = true
wsOpts.SessionCache = getClientSessionCache()
wsOpts.SkipCertVerify = v.option.SkipCertVerify
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &vmess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c = vmess.StreamHTTPConn(c, httpOpts)
default:
// handle TLS
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
}
c, err = vmess.StreamTLSConn(c, tlsOpts)
}
}
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", v.server) return nil, err
}
return v.client.StreamConn(c, parseVmessAddr(metadata))
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.addr)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
return newConn(c, v), err c, err = v.StreamConn(c, metadata)
return NewConn(c, v), err
} }
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
@ -57,12 +121,12 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel() defer cancel()
c, err := dialer.DialContext(ctx, "tcp", v.server) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", v.server) return nil, fmt.Errorf("%s connect error", v.addr)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata)) c, err = v.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
@ -75,14 +139,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
UUID: option.UUID, UUID: option.UUID,
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
TLS: option.TLS,
HostName: option.Server, HostName: option.Server,
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
NetWork: option.Network,
WebSocketPath: option.WSPath,
WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify,
SessionCache: getClientSessionCache(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -91,11 +149,12 @@ func NewVmess(option VmessOption) (*Vmess, error) {
return &Vmess{ return &Vmess{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess, tp: C.Vmess,
udp: true, udp: true,
}, },
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client, client: client,
option: &option,
}, nil }, nil
} }

View File

@ -56,6 +56,11 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
}) })
} }
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
proxy := f.findAliveProxy()
return proxy
}
func (f *Fallback) proxies() []C.Proxy { func (f *Fallback) proxies() []C.Proxy {
elm, _, _ := f.single.Do(func() (interface{}, error) { elm, _, _ := f.single.Do(func() (interface{}, error) {
return getProvidersProxies(f.providers), nil return getProvidersProxies(f.providers), nil
@ -77,7 +82,7 @@ func (f *Fallback) findAliveProxy() C.Proxy {
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
return &Fallback{ return &Fallback{
Base: outbound.NewBase(name, C.Fallback, false), Base: outbound.NewBase(name, "", C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
} }

View File

@ -59,20 +59,11 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
} }
}() }()
key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) proxy := lb.Unwrap(metadata)
proxies := lb.proxies()
buckets := int32(len(proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
if proxy.Alive() {
c, err = proxy.DialContext(ctx, metadata) c, err = proxy.DialContext(ctx, metadata)
return return
} }
}
c, err = proxies[0].DialContext(ctx, metadata)
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() { defer func() {
@ -81,6 +72,16 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
} }
}() }()
proxy := lb.Unwrap(metadata)
return proxy.DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
proxies := lb.proxies() proxies := lb.proxies()
buckets := int32(len(proxies)) buckets := int32(len(proxies))
@ -88,15 +89,11 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
idx := jumpHash(key, buckets) idx := jumpHash(key, buckets)
proxy := proxies[idx] proxy := proxies[idx]
if proxy.Alive() { if proxy.Alive() {
return proxy.DialUDP(metadata) return proxy
} }
} }
return proxies[0].DialUDP(metadata) return proxies[0]
}
func (lb *LoadBalance) SupportUDP() bool {
return true
} }
func (lb *LoadBalance) proxies() []C.Proxy { func (lb *LoadBalance) proxies() []C.Proxy {
@ -120,7 +117,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
return &LoadBalance{ return &LoadBalance{
Base: outbound.NewBase(name, C.LoadBalance, false), Base: outbound.NewBase(name, "", C.LoadBalance, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
maxRetry: 3, maxRetry: 3,
providers: providers, providers: providers,

View File

@ -64,7 +64,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
providers = append(providers, pd) providers = append(providers, pd)
} else { } else {
// select don't need health check // select don't need health check
if groupOption.Type == "select" { if groupOption.Type == "select" || groupOption.Type == "relay" {
hc := provider.NewHealthCheck(ps, "", 0) hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
@ -108,6 +108,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
group = NewFallback(groupName, providers) group = NewFallback(groupName, providers)
case "load-balance": case "load-balance":
group = NewLoadBalance(groupName, providers) group = NewLoadBalance(groupName, providers)
case "relay":
group = NewRelay(groupName, providers)
default: default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
} }

View File

@ -0,0 +1,98 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Relay struct {
*outbound.Base
single *singledo.Single
providers []provider.ProxyProvider
}
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies := r.proxies(metadata)
if len(proxies) == 0 {
return nil, errors.New("Proxy does not exist")
}
first := proxies[0]
last := proxies[len(proxies)-1]
c, err := dialer.DialContext(ctx, "tcp", first.Addr())
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
}
c, err = last.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
}
return outbound.NewConn(c, r), nil
}
func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range r.rawProxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": r.Type().String(),
"all": all,
})
}
func (r *Relay) rawProxies() []C.Proxy {
elm, _, _ := r.single.Do(func() (interface{}, error) {
return getProvidersProxies(r.providers), nil
})
return elm.([]C.Proxy)
}
func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
proxies := r.rawProxies()
for n, proxy := range proxies {
subproxy := proxy.Unwrap(metadata)
for subproxy != nil {
proxies[n] = subproxy
subproxy = subproxy.Unwrap(metadata)
}
}
return proxies
}
func NewRelay(name string, providers []provider.ProxyProvider) *Relay {
return &Relay{
Base: outbound.NewBase(name, "", C.Relay, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
}
}

View File

@ -14,12 +14,12 @@ import (
type Selector struct { type Selector struct {
*outbound.Base *outbound.Base
single *singledo.Single single *singledo.Single
selected C.Proxy selected string
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selected.DialContext(ctx, metadata) c, err := s.selectedProxy().DialContext(ctx, metadata)
if err == nil { if err == nil {
c.AppendToChains(s) c.AppendToChains(s)
} }
@ -27,7 +27,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
} }
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := s.selected.DialUDP(metadata) pc, err := s.selectedProxy().DialUDP(metadata)
if err == nil { if err == nil {
pc.AppendToChains(s) pc.AppendToChains(s)
} }
@ -35,12 +35,12 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
} }
func (s *Selector) SupportUDP() bool { func (s *Selector) SupportUDP() bool {
return s.selected.SupportUDP() return s.selectedProxy().SupportUDP()
} }
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range s.proxies() { for _, proxy := range getProvidersProxies(s.providers) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
@ -52,13 +52,14 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
} }
func (s *Selector) Now() string { func (s *Selector) Now() string {
return s.selected.Name() return s.selectedProxy().Name()
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
for _, proxy := range s.proxies() { for _, proxy := range getProvidersProxies(s.providers) {
if proxy.Name() == name { if proxy.Name() == name {
s.selected = proxy s.selected = name
s.single.Reset()
return nil return nil
} }
} }
@ -66,18 +67,29 @@ func (s *Selector) Set(name string) error {
return errors.New("Proxy does not exist") return errors.New("Proxy does not exist")
} }
func (s *Selector) proxies() []C.Proxy { func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
return s.selectedProxy()
}
func (s *Selector) selectedProxy() C.Proxy {
elm, _, _ := s.single.Do(func() (interface{}, error) { elm, _, _ := s.single.Do(func() (interface{}, error) {
return getProvidersProxies(s.providers), nil proxies := getProvidersProxies(s.providers)
for _, proxy := range proxies {
if proxy.Name() == s.selected {
return proxy, nil
}
}
return proxies[0], nil
}) })
return elm.([]C.Proxy) return elm.(C.Proxy)
} }
func NewSelector(name string, providers []provider.ProxyProvider) *Selector { func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0] selected := providers[0].Proxies()[0].Name()
return &Selector{ return &Selector{
Base: outbound.NewBase(name, C.Selector, false), Base: outbound.NewBase(name, "", C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
selected: selected, selected: selected,

View File

@ -38,6 +38,10 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
return u.fast()
}
func (u *URLTest) proxies() []C.Proxy { func (u *URLTest) proxies() []C.Proxy {
elm, _, _ := u.single.Do(func() (interface{}, error) { elm, _, _ := u.single.Do(func() (interface{}, error) {
return getProvidersProxies(u.providers), nil return getProvidersProxies(u.providers), nil
@ -86,7 +90,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
return &URLTest{ return &URLTest{
Base: outbound.NewBase(name, C.URLTest, false), Base: outbound.NewBase(name, "", C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10), fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers, providers: providers,

View File

@ -0,0 +1,53 @@
package outboundgroup
import (
"fmt"
"net"
"time"
C "github.com/Dreamacro/clash/constant"
)
func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
host, port, err := net.SplitHostPort(rawAddress)
if err != nil {
err = fmt.Errorf("addrToMetadata failed: %w", err)
return
}
ip := net.ParseIP(host)
if ip != nil {
if ip.To4() != nil {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip,
DstPort: port,
}
return
} else {
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
} else {
addr = &C.Metadata{
AddrType: C.AtypDomainName,
Host: host,
DstIP: nil,
DstPort: port,
}
return
}
}
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(30 * time.Second)
}
}

View File

@ -0,0 +1,154 @@
package provider
import (
"bytes"
"crypto/md5"
"io/ioutil"
"os"
"time"
"github.com/Dreamacro/clash/log"
)
var (
fileMode os.FileMode = 0666
)
type parser = func([]byte) (interface{}, error)
type fetcher struct {
name string
vehicle Vehicle
updatedAt *time.Time
ticker *time.Ticker
hash [16]byte
parser parser
onUpdate func(interface{})
}
func (f *fetcher) Name() string {
return f.name
}
func (f *fetcher) VehicleType() VehicleType {
return f.vehicle.Type()
}
func (f *fetcher) Initial() (interface{}, error) {
var buf []byte
var err error
var isLocal bool
if stat, err := os.Stat(f.vehicle.Path()); err == nil {
buf, err = ioutil.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
f.updatedAt = &modTime
isLocal = true
} else {
buf, err = f.vehicle.Read()
}
if err != nil {
return nil, err
}
proxies, err := f.parser(buf)
if err != nil {
if !isLocal {
return nil, err
}
// parse local file error, fallback to remote
buf, err = f.vehicle.Read()
if err != nil {
return nil, err
}
proxies, err = f.parser(buf)
if err != nil {
return nil, err
}
}
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
return nil, err
}
f.hash = md5.Sum(buf)
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
return proxies, nil
}
func (f *fetcher) Update() (interface{}, bool, error) {
buf, err := f.vehicle.Read()
if err != nil {
return nil, false, err
}
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now
return nil, true, nil
}
proxies, err := f.parser(buf)
if err != nil {
return nil, false, err
}
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
return nil, false, err
}
f.updatedAt = &now
f.hash = hash
return proxies, false, nil
}
func (f *fetcher) Destroy() error {
if f.ticker != nil {
f.ticker.Stop()
}
return nil
}
func (f *fetcher) pullLoop() {
for range f.ticker.C {
elm, same, err := f.Update()
if err != nil {
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
continue
}
if same {
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
continue
}
log.Infoln("[Provider] %s's proxies update", f.Name())
if f.onUpdate != nil {
f.onUpdate(elm)
}
}
}
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
return &fetcher{
name: name,
ticker: ticker,
vehicle: vehicle,
parser: parser,
onUpdate: onUpdate,
}
}

View File

@ -1,26 +1,20 @@
package provider package provider
import ( import (
"bytes"
"crypto/md5"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "runtime"
"os"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
const ( const (
ReservedName = "default" ReservedName = "default"
fileMode = 0666
) )
// Provider Type // Provider Type
@ -49,8 +43,7 @@ type Provider interface {
VehicleType() VehicleType VehicleType() VehicleType
Type() ProviderType Type() ProviderType
Initial() error Initial() error
Reload() error Update() error
Destroy() error
} }
// ProxyProvider interface // ProxyProvider interface
@ -58,24 +51,24 @@ type ProxyProvider interface {
Provider Provider
Proxies() []C.Proxy Proxies() []C.Proxy
HealthCheck() HealthCheck()
Update() error
} }
type ProxySchema struct { type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"` Proxies []map[string]interface{} `yaml:"proxies"`
} }
// for auto gc
type ProxySetProvider struct { type ProxySetProvider struct {
name string *proxySetProvider
vehicle Vehicle
hash [16]byte
proxies []C.Proxy
healthCheck *HealthCheck
ticker *time.Ticker
updatedAt *time.Time
} }
func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) { type proxySetProvider struct {
*fetcher
proxies []C.Proxy
healthCheck *HealthCheck
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"name": pp.Name(), "name": pp.Name(),
"type": pp.Type().String(), "type": pp.Type().String(),
@ -85,123 +78,41 @@ func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
}) })
} }
func (pp *ProxySetProvider) Name() string { func (pp *proxySetProvider) Name() string {
return pp.name return pp.name
} }
func (pp *ProxySetProvider) Reload() error { func (pp *proxySetProvider) HealthCheck() {
return nil
}
func (pp *ProxySetProvider) HealthCheck() {
pp.healthCheck.check() pp.healthCheck.check()
} }
func (pp *ProxySetProvider) Update() error { func (pp *proxySetProvider) Update() error {
return pp.pull() elm, same, err := pp.fetcher.Update()
if err == nil && !same {
pp.onUpdate(elm)
}
return err
} }
func (pp *ProxySetProvider) Destroy() error { func (pp *proxySetProvider) Initial() error {
pp.healthCheck.close() elm, err := pp.fetcher.Initial()
if err != nil {
if pp.ticker != nil { return err
pp.ticker.Stop()
} }
pp.onUpdate(elm)
return nil return nil
} }
func (pp *ProxySetProvider) Initial() error { func (pp *proxySetProvider) Type() ProviderType {
var buf []byte
var err error
if stat, err := os.Stat(pp.vehicle.Path()); err == nil {
buf, err = ioutil.ReadFile(pp.vehicle.Path())
modTime := stat.ModTime()
pp.updatedAt = &modTime
} else {
buf, err = pp.vehicle.Read()
}
if err != nil {
return err
}
proxies, err := pp.parse(buf)
if err != nil {
// parse local file error, fallback to remote
buf, err = pp.vehicle.Read()
if err != nil {
return err
}
}
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
return err
}
pp.hash = md5.Sum(buf)
pp.setProxies(proxies)
// pull proxies automatically
if pp.ticker != nil {
go pp.pullLoop()
}
return nil
}
func (pp *ProxySetProvider) VehicleType() VehicleType {
return pp.vehicle.Type()
}
func (pp *ProxySetProvider) Type() ProviderType {
return Proxy return Proxy
} }
func (pp *ProxySetProvider) Proxies() []C.Proxy { func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies return pp.proxies
} }
func (pp *ProxySetProvider) pullLoop() { func proxiesParse(buf []byte) (interface{}, error) {
for range pp.ticker.C {
if err := pp.pull(); err != nil {
log.Warnln("[Provider] %s pull error: %s", pp.Name(), err.Error())
}
}
}
func (pp *ProxySetProvider) pull() error {
buf, err := pp.vehicle.Read()
if err != nil {
return err
}
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(pp.hash[:], hash[:]) {
log.Debugln("[Provider] %s's proxies doesn't change", pp.Name())
pp.updatedAt = &now
return nil
}
proxies, err := pp.parse(buf)
if err != nil {
return err
}
log.Infoln("[Provider] %s's proxies update", pp.Name())
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
return err
}
pp.updatedAt = &now
pp.hash = hash
pp.setProxies(proxies)
return nil
}
func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{} schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil { if err := yaml.Unmarshal(buf, schema); err != nil {
@ -228,38 +139,52 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
return proxies, nil return proxies, nil
} }
func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) { func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies pp.proxies = proxies
pp.healthCheck.setProxy(proxies) pp.healthCheck.setProxy(proxies)
go pp.healthCheck.check() go pp.healthCheck.check()
} }
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { func stopProxyProvider(pd *ProxySetProvider) {
var ticker *time.Ticker pd.healthCheck.close()
if interval != 0 { pd.fetcher.Destroy()
ticker = time.NewTicker(interval)
} }
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
if hc.auto() { if hc.auto() {
go hc.process() go hc.process()
} }
return &ProxySetProvider{ pd := &proxySetProvider{
name: name,
vehicle: vehicle,
proxies: []C.Proxy{}, proxies: []C.Proxy{},
healthCheck: hc, healthCheck: hc,
ticker: ticker,
}
} }
onUpdate := func(elm interface{}) {
ret := elm.([]C.Proxy)
pd.setProxies(ret)
}
fetcher := newFetcher(name, interval, vehicle, proxiesParse, onUpdate)
pd.fetcher = fetcher
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper
}
// for auto gc
type CompatibleProvider struct { type CompatibleProvider struct {
*compatibleProvider
}
type compatibleProvider struct {
name string name string
healthCheck *HealthCheck healthCheck *HealthCheck
proxies []C.Proxy proxies []C.Proxy
} }
func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"name": cp.Name(), "name": cp.Name(),
"type": cp.Type().String(), "type": cp.Type().String(),
@ -268,43 +193,38 @@ func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) {
}) })
} }
func (cp *CompatibleProvider) Name() string { func (cp *compatibleProvider) Name() string {
return cp.name return cp.name
} }
func (cp *CompatibleProvider) Reload() error { func (cp *compatibleProvider) HealthCheck() {
return nil
}
func (cp *CompatibleProvider) Destroy() error {
cp.healthCheck.close()
return nil
}
func (cp *CompatibleProvider) HealthCheck() {
cp.healthCheck.check() cp.healthCheck.check()
} }
func (cp *CompatibleProvider) Update() error { func (cp *compatibleProvider) Update() error {
return nil return nil
} }
func (cp *CompatibleProvider) Initial() error { func (cp *compatibleProvider) Initial() error {
return nil return nil
} }
func (cp *CompatibleProvider) VehicleType() VehicleType { func (cp *compatibleProvider) VehicleType() VehicleType {
return Compatible return Compatible
} }
func (cp *CompatibleProvider) Type() ProviderType { func (cp *compatibleProvider) Type() ProviderType {
return Proxy return Proxy
} }
func (cp *CompatibleProvider) Proxies() []C.Proxy { func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies return cp.proxies
} }
func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close()
}
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 { if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least") return nil, errors.New("Provider need one proxy at least")
@ -314,9 +234,13 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
go hc.process() go hc.process()
} }
return &CompatibleProvider{ pd := &compatibleProvider{
name: name, name: name,
proxies: proxies, proxies: proxies,
healthCheck: hc, healthCheck: hc,
}, nil }
wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
return wrapper, nil
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@ -75,10 +76,21 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel() defer cancel()
req, err := http.NewRequest(http.MethodGet, h.url, nil) uri, err := url.Parse(h.url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
if err != nil {
return nil, err
}
if user := uri.User; user != nil {
password, _ := user.Password()
req.SetBasicAuth(user.Username(), password)
}
req = req.WithContext(ctx) req = req.WithContext(ctx)
transport := &http.Transport{ transport := &http.Transport{

View File

@ -42,6 +42,14 @@ func WithSize(maxSize int) Option {
} }
} }
// WithStale decide whether Stale return is enabled.
// If this feature is enabled, element will not get Evicted according to `WithAge`.
func WithStale(stale bool) Option {
return func(l *LruCache) {
l.staleReturn = stale
}
}
// LruCache is a thread-safe, in-memory lru-cache that evicts the // LruCache is a thread-safe, in-memory lru-cache that evicts the
// least recently used entries from memory when (if set) the entries are // least recently used entries from memory when (if set) the entries are
// older than maxAge (in seconds). Use the New constructor to create one. // older than maxAge (in seconds). Use the New constructor to create one.
@ -52,6 +60,7 @@ type LruCache struct {
cache map[interface{}]*list.Element cache map[interface{}]*list.Element
lru *list.List // Front is least-recent lru *list.List // Front is least-recent
updateAgeOnGet bool updateAgeOnGet bool
staleReturn bool
onEvict EvictCallback onEvict EvictCallback
} }
@ -72,31 +81,28 @@ func NewLRUCache(options ...Option) *LruCache {
// Get returns the interface{} representation of a cached response and a bool // Get returns the interface{} representation of a cached response and a bool
// set to true if the key was found. // set to true if the key was found.
func (c *LruCache) Get(key interface{}) (interface{}, bool) { func (c *LruCache) Get(key interface{}) (interface{}, bool) {
c.mu.Lock() entry := c.get(key)
defer c.mu.Unlock() if entry == nil {
le, ok := c.cache[key]
if !ok {
return nil, false return nil, false
} }
if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
return nil, false
}
c.lru.MoveToBack(le)
entry := le.Value.(*entry)
if c.maxAge > 0 && c.updateAgeOnGet {
entry.expires = time.Now().Unix() + c.maxAge
}
value := entry.value value := entry.value
return value, true return value, true
} }
// GetWithExpire returns the interface{} representation of a cached response,
// a time.Time Give expected expires,
// and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool) {
entry := c.get(key)
if entry == nil {
return nil, time.Time{}, false
}
return entry.value, time.Unix(entry.expires, 0), true
}
// Exist returns if key exist in cache but not put item to the head of linked list // Exist returns if key exist in cache but not put item to the head of linked list
func (c *LruCache) Exist(key interface{}) bool { func (c *LruCache) Exist(key interface{}) bool {
c.mu.Lock() c.mu.Lock()
@ -108,21 +114,26 @@ func (c *LruCache) Exist(key interface{}) bool {
// Set stores the interface{} representation of a response for a given key. // Set stores the interface{} representation of a response for a given key.
func (c *LruCache) Set(key interface{}, value interface{}) { func (c *LruCache) Set(key interface{}, value interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
expires := int64(0) expires := int64(0)
if c.maxAge > 0 { if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge expires = time.Now().Unix() + c.maxAge
} }
c.SetWithExpire(key, value, time.Unix(expires, 0))
}
// SetWithExpire stores the interface{} representation of a response for a given key and given exires.
// The expires time will round to second.
func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
if le, ok := c.cache[key]; ok { if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le) c.lru.MoveToBack(le)
e := le.Value.(*entry) e := le.Value.(*entry)
e.value = value e.value = value
e.expires = expires e.expires = expires.Unix()
} else { } else {
e := &entry{key: key, value: value, expires: expires} e := &entry{key: key, value: value, expires: expires.Unix()}
c.cache[key] = c.lru.PushBack(e) c.cache[key] = c.lru.PushBack(e)
if c.maxSize > 0 { if c.maxSize > 0 {
@ -135,6 +146,30 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.maybeDeleteOldest() c.maybeDeleteOldest()
} }
func (c *LruCache) get(key interface{}) *entry {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key]
if !ok {
return nil
}
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
return nil
}
c.lru.MoveToBack(le)
entry := le.Value.(*entry)
if c.maxAge > 0 && c.updateAgeOnGet {
entry.expires = time.Now().Unix() + c.maxAge
}
return entry
}
// Delete removes the value associated with a key. // Delete removes the value associated with a key.
func (c *LruCache) Delete(key string) { func (c *LruCache) Delete(key string) {
c.mu.Lock() c.mu.Lock()
@ -147,7 +182,7 @@ func (c *LruCache) Delete(key string) {
} }
func (c *LruCache) maybeDeleteOldest() { func (c *LruCache) maybeDeleteOldest() {
if c.maxAge > 0 { if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix() now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() { for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
c.deleteElement(le) c.deleteElement(le)

View File

@ -136,3 +136,31 @@ func TestEvict(t *testing.T) {
assert.Equal(t, temp, 3) assert.Equal(t, temp, 3)
} }
func TestSetWithExpire(t *testing.T) {
c := NewLRUCache(WithAge(1))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, 2, tenSecBefore)
// res is expected not to exist, and expires should be empty time.Time
res, expires, exist := c.GetWithExpire(1)
assert.Equal(t, nil, res)
assert.Equal(t, time.Time{}, expires)
assert.Equal(t, false, exist)
}
func TestStale(t *testing.T) {
c := NewLRUCache(WithAge(1), WithStale(true))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, 2, tenSecBefore)
res, expires, exist := c.GetWithExpire(1)
assert.Equal(t, 2, res)
assert.Equal(t, tenSecBefore, expires)
assert.Equal(t, true, exist)
}

View File

@ -16,7 +16,9 @@ type Picker struct {
wg sync.WaitGroup wg sync.WaitGroup
once sync.Once once sync.Once
errOnce sync.Once
result interface{} result interface{}
err error
} }
func newPicker(ctx context.Context, cancel func()) *Picker { func newPicker(ctx context.Context, cancel func()) *Picker {
@ -49,6 +51,11 @@ func (p *Picker) Wait() interface{} {
return p.result return p.result
} }
// Error return the first error (if all success return nil)
func (p *Picker) Error() error {
return p.err
}
// Go calls the given function in a new goroutine. // Go calls the given function in a new goroutine.
// The first call to return a nil error cancels the group; its result will be returned by Wait. // The first call to return a nil error cancels the group; its result will be returned by Wait.
func (p *Picker) Go(f func() (interface{}, error)) { func (p *Picker) Go(f func() (interface{}, error)) {
@ -64,6 +71,10 @@ func (p *Picker) Go(f func() (interface{}, error)) {
p.cancel() p.cancel()
} }
}) })
} else {
p.errOnce.Do(func() {
p.err = err
})
} }
}() }()
} }

View File

@ -36,4 +36,5 @@ func TestPicker_Timeout(t *testing.T) {
number := picker.Wait() number := picker.Wait()
assert.Nil(t, number) assert.Nil(t, number)
assert.NotNil(t, picker.Error())
} }

65
common/pool/alloc.go Normal file
View File

@ -0,0 +1,65 @@
package pool
// Inspired by https://github.com/xtaci/smux/blob/master/alloc.go
import (
"errors"
"math/bits"
"sync"
)
var defaultAllocator *Allocator
func init() {
defaultAllocator = NewAllocator()
}
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
type Allocator struct {
buffers []sync.Pool
}
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed to be
// no more than 50%.
func NewAllocator() *Allocator {
alloc := new(Allocator)
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
for k := range alloc.buffers {
i := k
alloc.buffers[k].New = func() interface{} {
return make([]byte, 1<<uint32(i))
}
}
return alloc
}
// Get a []byte from pool with most appropriate cap
func (alloc *Allocator) Get(size int) []byte {
if size <= 0 || size > 65536 {
return nil
}
bits := msb(size)
if size == 1<<bits {
return alloc.buffers[bits].Get().([]byte)[:size]
}
return alloc.buffers[bits+1].Get().([]byte)[:size]
}
// Put returns a []byte to pool for future use,
// which the cap must be exactly 2^n
func (alloc *Allocator) Put(buf []byte) error {
bits := msb(cap(buf))
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
return errors.New("allocator Put() incorrect buffer size")
}
alloc.buffers[bits].Put(buf)
return nil
}
// msb return the pos of most significiant bit
func msb(size int) uint16 {
return uint16(bits.Len32(uint32(size)) - 1)
}

48
common/pool/alloc_test.go Normal file
View File

@ -0,0 +1,48 @@
package pool
import (
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAllocGet(t *testing.T) {
alloc := NewAllocator()
assert.Nil(t, alloc.Get(0))
assert.Equal(t, 1, len(alloc.Get(1)))
assert.Equal(t, 2, len(alloc.Get(2)))
assert.Equal(t, 3, len(alloc.Get(3)))
assert.Equal(t, 4, cap(alloc.Get(3)))
assert.Equal(t, 4, cap(alloc.Get(4)))
assert.Equal(t, 1023, len(alloc.Get(1023)))
assert.Equal(t, 1024, cap(alloc.Get(1023)))
assert.Equal(t, 1024, len(alloc.Get(1024)))
assert.Equal(t, 65536, len(alloc.Get(65536)))
assert.Nil(t, alloc.Get(65537))
}
func TestAllocPut(t *testing.T) {
alloc := NewAllocator()
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 65536, 65536)), "put elem:65536 []bytes misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior")
}
func TestAllocPutThenGet(t *testing.T) {
alloc := NewAllocator()
data := alloc.Get(4)
alloc.Put(data)
newData := alloc.Get(4)
assert.Equal(t, cap(data), cap(newData), "different cap while alloc.Get()")
}
func BenchmarkMSB(b *testing.B) {
for i := 0; i < b.N; i++ {
msb(rand.Int())
}
}

View File

@ -1,15 +1,16 @@
package pool package pool
import (
"sync"
)
const ( const (
// io.Copy default buffer size is 32 KiB // io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB // but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay // so define a buffer of 20 KiB to reduce the memory of each TCP relay
bufferSize = 20 * 1024 RelayBufferSize = 20 * 1024
) )
// BufPool provide buffer for relay func Get(size int) []byte {
var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} return defaultAllocator.Get(size)
}
func Put(buf []byte) error {
return defaultAllocator.Put(buf)
}

View File

@ -50,6 +50,10 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s
return call.val, call.err, false return call.val, call.err, false
} }
func (s *Single) Reset() {
s.last = time.Time{}
}
func NewSingle(wait time.Duration) *Single { func NewSingle(wait time.Duration) *Single {
return &Single{wait: wait} return &Single{wait: wait}
} }

View File

@ -19,7 +19,7 @@ func TestBasic(t *testing.T) {
} }
var wg sync.WaitGroup var wg sync.WaitGroup
const n = 10 const n = 5
wg.Add(n) wg.Add(n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
go func() { go func() {
@ -33,7 +33,7 @@ func TestBasic(t *testing.T) {
wg.Wait() wg.Wait()
assert.Equal(t, 1, foo) assert.Equal(t, 1, foo)
assert.Equal(t, 9, shardCount) assert.Equal(t, 4, shardCount)
} }
func TestTimer(t *testing.T) { func TestTimer(t *testing.T) {
@ -51,3 +51,18 @@ func TestTimer(t *testing.T) {
assert.Equal(t, 1, foo) assert.Equal(t, 1, foo)
assert.True(t, shard) assert.True(t, shard)
} }
func TestReset(t *testing.T) {
single := NewSingle(time.Millisecond * 30)
foo := 0
call := func() (interface{}, error) {
foo++
return nil, nil
}
single.Do(call)
single.Reset()
single.Do(call)
assert.Equal(t, 2, foo)
}

View File

@ -0,0 +1,19 @@
package sockopt
import (
"net"
"syscall"
)
func UDPReuseaddr(c *net.UDPConn) (err error) {
rc, err := c.SyscallConn()
if err != nil {
return
}
rc.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
})
return
}

View File

@ -0,0 +1,11 @@
// +build !linux
package sockopt
import (
"net"
)
func UDPReuseaddr(c *net.UDPConn) (err error) {
return
}

View File

@ -216,6 +216,11 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
} }
v := dataVal.MapIndex(k).Interface() v := dataVal.MapIndex(k).Interface()
if v == nil {
errors = append(errors, fmt.Sprintf("filed %s invalid", fieldName))
continue
}
currentVal := reflect.Indirect(reflect.New(valElemType)) currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil { if err := d.decode(fieldName, v, currentVal); err != nil {
errors = append(errors, err.Error()) errors = append(errors, err.Error())

View File

@ -8,22 +8,26 @@ import (
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
) )
func Dialer() *net.Dialer { func Dialer() (*net.Dialer, error) {
dialer := &net.Dialer{} dialer := &net.Dialer{}
if DialerHook != nil { if DialerHook != nil {
DialerHook(dialer) if err := DialerHook(dialer); err != nil {
return nil, err
}
} }
return dialer return dialer, nil
} }
func ListenConfig() *net.ListenConfig { func ListenConfig() (*net.ListenConfig, error) {
cfg := &net.ListenConfig{} cfg := &net.ListenConfig{}
if ListenConfigHook != nil { if ListenConfigHook != nil {
ListenConfigHook(cfg) if err := ListenConfigHook(cfg); err != nil {
return nil, err
}
} }
return cfg return cfg, nil
} }
func Dial(network, address string) (net.Conn, error) { func Dial(network, address string) (net.Conn, error) {
@ -38,7 +42,10 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
return nil, err return nil, err
} }
dialer := Dialer() dialer, err := Dialer()
if err != nil {
return nil, err
}
var ip net.IP var ip net.IP
switch network { switch network {
@ -53,7 +60,9 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
} }
if DialHook != nil { if DialHook != nil {
DialHook(dialer, network, ip) if err := DialHook(dialer, network, ip); err != nil {
return nil, err
}
} }
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
case "tcp", "udp": case "tcp", "udp":
@ -64,13 +73,17 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
} }
func ListenPacket(network, address string) (net.PacketConn, error) { func ListenPacket(network, address string) (net.PacketConn, error) {
lc := ListenConfig() lc, err := ListenConfig()
if err != nil {
return nil, err
}
if ListenPacketHook != nil && address == "" { if ListenPacketHook != nil && address == "" {
ip := ListenPacketHook() ip, err := ListenPacketHook()
if ip != nil { if err != nil {
address = net.JoinHostPort(ip.String(), "0") return nil, err
} }
address = net.JoinHostPort(ip.String(), "0")
} }
return lc.ListenPacket(context.Background(), network, address) return lc.ListenPacket(context.Background(), network, address)
} }
@ -95,7 +108,6 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con
var primary, fallback dialResult var primary, fallback dialResult
startRacer := func(ctx context.Context, network, host string, ipv6 bool) { startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
dialer := Dialer()
result := dialResult{ipv6: ipv6, done: true} result := dialResult{ipv6: ipv6, done: true}
defer func() { defer func() {
select { select {
@ -107,6 +119,12 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con
} }
}() }()
dialer, err := Dialer()
if err != nil {
result.error = err
return
}
var ip net.IP var ip net.IP
if ipv6 { if ipv6 {
ip, result.error = resolver.ResolveIPv6(host) ip, result.error = resolver.ResolveIPv6(host)
@ -119,7 +137,9 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con
result.resolved = true result.resolved = true
if DialHook != nil { if DialHook != nil {
DialHook(dialer, network, ip) if result.error = DialHook(dialer, network, ip); result.error != nil {
return
}
} }
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
} }

View File

@ -8,10 +8,10 @@ import (
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
) )
type DialerHookFunc = func(dialer *net.Dialer) type DialerHookFunc = func(dialer *net.Dialer) error
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
type ListenConfigHookFunc = func(*net.ListenConfig) type ListenConfigHookFunc = func(*net.ListenConfig) error
type ListenPacketHookFunc = func() net.IP type ListenPacketHookFunc = func() (net.IP, error)
var ( var (
DialerHook DialerHookFunc DialerHook DialerHookFunc
@ -70,7 +70,7 @@ func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
func ListenPacketWithInterface(name string) ListenPacketHookFunc { func ListenPacketWithInterface(name string) ListenPacketHookFunc {
single := singledo.NewSingle(5 * time.Second) single := singledo.NewSingle(5 * time.Second)
return func() net.IP { return func() (net.IP, error) {
elm, err, _ := single.Do(func() (interface{}, error) { elm, err, _ := single.Do(func() (interface{}, error) {
iface, err := net.InterfaceByName(name) iface, err := net.InterfaceByName(name)
if err != nil { if err != nil {
@ -86,7 +86,7 @@ func ListenPacketWithInterface(name string) ListenPacketHookFunc {
}) })
if err != nil { if err != nil {
return nil return nil, err
} }
addrs := elm.([]net.Addr) addrs := elm.([]net.Addr)
@ -97,17 +97,17 @@ func ListenPacketWithInterface(name string) ListenPacketHookFunc {
continue continue
} }
return addr.IP return addr.IP, nil
} }
return nil return nil, ErrAddrNotFound
} }
} }
func DialerWithInterface(name string) DialHookFunc { func DialerWithInterface(name string) DialHookFunc {
single := singledo.NewSingle(5 * time.Second) single := singledo.NewSingle(5 * time.Second)
return func(dialer *net.Dialer, network string, ip net.IP) { return func(dialer *net.Dialer, network string, ip net.IP) error {
elm, err, _ := single.Do(func() (interface{}, error) { elm, err, _ := single.Do(func() (interface{}, error) {
iface, err := net.InterfaceByName(name) iface, err := net.InterfaceByName(name)
if err != nil { if err != nil {
@ -123,7 +123,7 @@ func DialerWithInterface(name string) DialHookFunc {
}) })
if err != nil { if err != nil {
return return err
} }
addrs := elm.([]net.Addr) addrs := elm.([]net.Addr)
@ -132,11 +132,17 @@ func DialerWithInterface(name string) DialHookFunc {
case "tcp", "tcp4", "tcp6": case "tcp", "tcp4", "tcp6":
if addr, err := lookupTCPAddr(ip, addrs); err == nil { if addr, err := lookupTCPAddr(ip, addrs); err == nil {
dialer.LocalAddr = addr dialer.LocalAddr = addr
} else {
return err
} }
case "udp", "udp4", "udp6": case "udp", "udp4", "udp6":
if addr, err := lookupUDPAddr(ip, addrs); err == nil { if addr, err := lookupUDPAddr(ip, addrs); err == nil {
dialer.LocalAddr = addr dialer.LocalAddr = addr
} else {
return err
} }
} }
return nil
} }
} }

View File

@ -7,6 +7,7 @@ import (
const ( const (
wildcard = "*" wildcard = "*"
dotWildcard = ""
domainStep = "." domainStep = "."
) )
@ -21,8 +22,23 @@ type Trie struct {
root *Node root *Node
} }
func isValidDomain(domain string) bool { func validAndSplitDomain(domain string) ([]string, bool) {
return domain != "" && domain[0] != '.' && domain[len(domain)-1] != '.' if domain != "" && domain[len(domain)-1] == '.' {
return nil, false
}
parts := strings.Split(domain, domainStep)
if len(parts) == 1 {
return nil, false
}
for _, part := range parts[1:] {
if part == "" {
return nil, false
}
}
return parts, true
} }
// Insert adds a node to the trie. // Insert adds a node to the trie.
@ -30,12 +46,13 @@ func isValidDomain(domain string) bool {
// 1. www.example.com // 1. www.example.com
// 2. *.example.com // 2. *.example.com
// 3. subdomain.*.example.com // 3. subdomain.*.example.com
// 4. .example.com
func (t *Trie) Insert(domain string, data interface{}) error { func (t *Trie) Insert(domain string, data interface{}) error {
if !isValidDomain(domain) { parts, valid := validAndSplitDomain(domain)
if !valid {
return ErrInvalidDomain return ErrInvalidDomain
} }
parts := strings.Split(domain, domainStep)
node := t.root node := t.root
// reverse storage domain part to save space // reverse storage domain part to save space
for i := len(parts) - 1; i >= 0; i-- { for i := len(parts) - 1; i >= 0; i-- {
@ -55,28 +72,45 @@ func (t *Trie) Insert(domain string, data interface{}) error {
// Priority as: // Priority as:
// 1. static part // 1. static part
// 2. wildcard domain // 2. wildcard domain
// 2. dot wildcard domain
func (t *Trie) Search(domain string) *Node { func (t *Trie) Search(domain string) *Node {
if !isValidDomain(domain) { parts, valid := validAndSplitDomain(domain)
if !valid || parts[0] == "" {
return nil return nil
} }
parts := strings.Split(domain, domainStep)
n := t.root n := t.root
var dotWildcardNode *Node
var wildcardNode *Node
for i := len(parts) - 1; i >= 0; i-- { for i := len(parts) - 1; i >= 0; i-- {
part := parts[i] part := parts[i]
var child *Node if node := n.getChild(dotWildcard); node != nil {
if !n.hasChild(part) { dotWildcardNode = node
if !n.hasChild(wildcard) {
return nil
} }
child = n.getChild(wildcard) child := n.getChild(part)
} else { if child == nil && wildcardNode != nil {
child = n.getChild(part) child = wildcardNode.getChild(part)
} }
wildcardNode = n.getChild(wildcard)
n = child n = child
if n == nil {
n = wildcardNode
wildcardNode = nil
}
if n == nil {
break
}
}
if n == nil {
if dotWildcardNode != nil {
return dotWildcardNode
}
return nil
} }
if n.Data == nil { if n.Data == nil {

View File

@ -3,6 +3,8 @@ package trie
import ( import (
"net" "net"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
var localIP = net.IP{127, 0, 0, 1} var localIP = net.IP{127, 0, 0, 1}
@ -19,17 +21,9 @@ func TestTrie_Basic(t *testing.T) {
} }
node := tree.Search("example.com") node := tree.Search("example.com")
if node == nil { assert.NotNil(t, node)
t.Error("should not recv nil") assert.True(t, node.Data.(net.IP).Equal(localIP))
} assert.NotNil(t, tree.Insert("", localIP))
if !node.Data.(net.IP).Equal(localIP) {
t.Error("should equal 127.0.0.1")
}
if tree.Insert("", localIP) == nil {
t.Error("should return error")
}
} }
func TestTrie_Wildcard(t *testing.T) { func TestTrie_Wildcard(t *testing.T) {
@ -38,50 +32,56 @@ func TestTrie_Wildcard(t *testing.T) {
"*.example.com", "*.example.com",
"sub.*.example.com", "sub.*.example.com",
"*.dev", "*.dev",
".org",
".example.net",
".apple.*",
} }
for _, domain := range domains { for _, domain := range domains {
tree.Insert(domain, localIP) tree.Insert(domain, localIP)
} }
if tree.Search("sub.example.com") == nil { assert.NotNil(t, tree.Search("sub.example.com"))
t.Error("should not recv nil") assert.NotNil(t, tree.Search("sub.foo.example.com"))
assert.NotNil(t, tree.Search("test.org"))
assert.NotNil(t, tree.Search("test.example.net"))
assert.NotNil(t, tree.Search("test.apple.com"))
assert.Nil(t, tree.Search("foo.sub.example.com"))
assert.Nil(t, tree.Search("foo.example.dev"))
assert.Nil(t, tree.Search("example.com"))
} }
if tree.Search("sub.foo.example.com") == nil { func TestTrie_Priority(t *testing.T) {
t.Error("should not recv nil") tree := New()
domains := []string{
".dev",
"example.dev",
"*.example.dev",
"test.example.dev",
} }
if tree.Search("foo.sub.example.com") != nil { assertFn := func(domain string, data int) {
t.Error("should recv nil") node := tree.Search(domain)
assert.NotNil(t, node)
assert.Equal(t, data, node.Data)
} }
if tree.Search("foo.example.dev") != nil { for idx, domain := range domains {
t.Error("should recv nil") tree.Insert(domain, idx)
} }
if tree.Search("example.com") != nil { assertFn("test.dev", 0)
t.Error("should recv nil") assertFn("foo.bar.dev", 0)
} assertFn("example.dev", 1)
assertFn("foo.example.dev", 2)
assertFn("test.example.dev", 3)
} }
func TestTrie_Boundary(t *testing.T) { func TestTrie_Boundary(t *testing.T) {
tree := New() tree := New()
tree.Insert("*.dev", localIP) tree.Insert("*.dev", localIP)
if err := tree.Insert(".", localIP); err == nil { assert.NotNil(t, tree.Insert(".", localIP))
t.Error("should recv err") assert.NotNil(t, tree.Insert("..dev", localIP))
} assert.Nil(t, tree.Search("dev"))
if err := tree.Insert(".com", localIP); err == nil {
t.Error("should recv err")
}
if tree.Search("dev") != nil {
t.Error("should recv nil")
}
if tree.Search(".dev") != nil {
t.Error("should recv nil")
}
} }

View File

@ -22,6 +22,14 @@ func LoadFromBytes(buffer []byte) {
}) })
} }
func Verify() bool {
instance, err := geoip2.Open(C.Path.MMDB())
if err == nil {
instance.Close()
}
return err == nil
}
func Instance() *geoip2.Reader { func Instance() *geoip2.Reader {
once.Do(func() { once.Do(func() {
var err error var err error

View File

@ -34,15 +34,15 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
} }
if ho.firstResponse { if ho.firstResponse {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
n, err := ho.Conn.Read(buf) n, err := ho.Conn.Read(buf)
if err != nil { if err != nil {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
return 0, err return 0, err
} }
idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
if idx == -1 { if idx == -1 {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
return 0, io.EOF return 0, io.EOF
} }
ho.firstResponse = false ho.firstResponse = false
@ -52,7 +52,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
ho.buf = buf[:idx+4+length] ho.buf = buf[:idx+4+length]
ho.offset = idx + 4 + n ho.offset = idx + 4 + n
} else { } else {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
} }
return n, nil return n, nil
} }

View File

@ -29,12 +29,12 @@ type TLSObfs struct {
} }
func (to *TLSObfs) read(b []byte, discardN int) (int, error) { func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(discardN)
_, err := io.ReadFull(to.Conn, buf[:discardN]) _, err := io.ReadFull(to.Conn, buf)
if err != nil { if err != nil {
return 0, err return 0, err
} }
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
sizeBuf := make([]byte, 2) sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf) _, err = io.ReadFull(to.Conn, sizeBuf)
@ -102,15 +102,11 @@ func (to *TLSObfs) write(b []byte) (int, error) {
return len(b), err return len(b), err
} }
size := pool.BufPool.Get().([]byte)
binary.BigEndian.PutUint16(size[:2], uint16(len(b)))
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
buf.Write([]byte{0x17, 0x03, 0x03}) buf.Write([]byte{0x17, 0x03, 0x03})
buf.Write(size[:2]) binary.Write(buf, binary.BigEndian, uint16(len(b)))
buf.Write(b) buf.Write(b)
_, err := to.Conn.Write(buf.Bytes()) _, err := to.Conn.Write(buf.Bytes())
pool.BufPool.Put(size[:cap(size)])
return len(b), err return len(b), err
} }

View File

@ -178,7 +178,7 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr,
} }
command = buf[1] command = buf[1]
addr, err = readAddr(rw, buf) addr, err = ReadAddr(rw, buf)
if err != nil { if err != nil {
return return
} }
@ -260,10 +260,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
return nil, err return nil, err
} }
return readAddr(rw, buf) return ReadAddr(rw, buf)
} }
func readAddr(r io.Reader, b []byte) (Addr, error) { func ReadAddr(r io.Reader, b []byte) (Addr, error) {
if len(b) < MaxAddrLen { if len(b) < MaxAddrLen {
return nil, io.ErrShortBuffer return nil, io.ErrShortBuffer
} }

223
component/trojan/trojan.go Normal file
View File

@ -0,0 +1,223 @@
package trojan
import (
"bytes"
"crypto/sha256"
"crypto/tls"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"net"
"sync"
"github.com/Dreamacro/clash/component/socks5"
)
const (
// max packet length
maxLength = 8192
)
var (
defaultALPN = []string{"h2", "http/1.1"}
crlf = []byte{'\r', '\n'}
bufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
)
type Command = byte
var (
CommandTCP byte = 1
CommandUDP byte = 3
)
type Option struct {
Password string
ALPN []string
ServerName string
SkipCertVerify bool
ClientSessionCache tls.ClientSessionCache
}
type Trojan struct {
option *Option
hexPassword []byte
}
func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
alpn := defaultALPN
if len(t.option.ALPN) != 0 {
alpn = t.option.ALPN
}
tlsConfig := &tls.Config{
NextProtos: alpn,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: t.option.SkipCertVerify,
ServerName: t.option.ServerName,
ClientSessionCache: t.option.ClientSessionCache,
}
tlsConn := tls.Client(conn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
return nil, err
}
return tlsConn, nil
}
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
buf := bufPool.Get().(*bytes.Buffer)
defer buf.Reset()
defer bufPool.Put(buf)
buf.Write(t.hexPassword)
buf.Write(crlf)
buf.WriteByte(command)
buf.Write(socks5Addr)
buf.Write(crlf)
_, err := w.Write(buf.Bytes())
return err
}
func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
return &PacketConn{
Conn: conn,
}
}
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
buf := bufPool.Get().(*bytes.Buffer)
defer buf.Reset()
defer bufPool.Put(buf)
buf.Write(socks5Addr)
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
buf.Write(crlf)
buf.Write(payload)
return w.Write(buf.Bytes())
}
func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
if len(payload) <= maxLength {
return writePacket(w, socks5Addr, payload)
}
offset := 0
total := len(payload)
for {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := writePacket(w, socks5Addr, payload[offset:cursor])
if err != nil {
return offset + n, err
}
offset = cursor
if offset == total {
break
}
}
return total, nil
}
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
addr, err := socks5.ReadAddr(r, payload)
if err != nil {
return nil, 0, 0, errors.New("read addr error")
}
uAddr := addr.UDPAddr()
if _, err = io.ReadFull(r, payload[:2]); err != nil {
return nil, 0, 0, errors.New("read length error")
}
total := int(binary.BigEndian.Uint16(payload[:2]))
if total > maxLength {
return nil, 0, 0, errors.New("packet invalid")
}
// read crlf
if _, err = io.ReadFull(r, payload[:2]); err != nil {
return nil, 0, 0, errors.New("read crlf error")
}
length := len(payload)
if total < length {
length = total
}
if _, err = io.ReadFull(r, payload[:length]); err != nil {
return nil, 0, 0, errors.New("read packet error")
}
return uAddr, length, total - length, nil
}
func New(option *Option) *Trojan {
return &Trojan{option, hexSha224([]byte(option.Password))}
}
type PacketConn struct {
net.Conn
remain int
rAddr net.Addr
mux sync.Mutex
}
func (pc *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return WritePacket(pc, socks5.ParseAddr(addr.String()), b)
}
func (pc *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
pc.mux.Lock()
defer pc.mux.Unlock()
if pc.remain != 0 {
length := len(b)
if pc.remain < length {
length = pc.remain
}
n, err := pc.Conn.Read(b[:length])
if err != nil {
return 0, nil, err
}
pc.remain -= n
addr := pc.rAddr
if pc.remain == 0 {
pc.rAddr = nil
}
return n, addr, nil
}
addr, n, remain, err := ReadPacket(pc.Conn, b)
if err != nil {
return 0, nil, err
}
if remain != 0 {
pc.remain = remain
pc.rAddr = addr
}
return n, addr, nil
}
func hexSha224(data []byte) []byte {
buf := make([]byte, 56)
hash := sha256.New224()
hash.Write(data)
hex.Encode(buf, hash.Sum(nil))
return buf
}

View File

@ -11,9 +11,12 @@ import (
// Option is options of websocket obfs // Option is options of websocket obfs
type Option struct { type Option struct {
Host string Host string
Port string
Path string Path string
Headers map[string]string Headers map[string]string
TLSConfig *tls.Config TLS bool
SkipCertVerify bool
SessionCache tls.ClientSessionCache
Mux bool Mux bool
} }
@ -26,14 +29,16 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) {
config := &vmess.WebsocketConfig{ config := &vmess.WebsocketConfig{
Host: option.Host, Host: option.Host,
Port: option.Port,
Path: option.Path, Path: option.Path,
TLS: option.TLSConfig != nil, TLS: option.TLS,
Headers: header, Headers: header,
TLSConfig: option.TLSConfig, SkipCertVerify: option.SkipCertVerify,
SessionCache: option.SessionCache,
} }
var err error var err error
conn, err = vmess.NewWebsocketConn(conn, config) conn, err = vmess.StreamWebsocketConn(conn, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -22,8 +22,8 @@ func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter {
} }
func (w *aeadWriter) Write(b []byte) (n int, err error) { func (w *aeadWriter) Write(b []byte) (n int, err error) {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
defer pool.BufPool.Put(buf[:cap(buf)]) defer pool.Put(buf)
length := len(b) length := len(b)
for { for {
if length == 0 { if length == 0 {
@ -73,7 +73,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
n := copy(b, r.buf[r.offset:]) n := copy(b, r.buf[r.offset:])
r.offset += n r.offset += n
if r.offset == len(r.buf) { if r.offset == len(r.buf) {
pool.BufPool.Put(r.buf[:cap(r.buf)]) pool.Put(r.buf)
r.buf = nil r.buf = nil
} }
return n, nil return n, nil
@ -89,10 +89,10 @@ func (r *aeadReader) Read(b []byte) (int, error) {
return 0, errors.New("Buffer is larger than standard") return 0, errors.New("Buffer is larger than standard")
} }
buf := pool.BufPool.Get().([]byte) buf := pool.Get(size)
_, err = io.ReadFull(r.Reader, buf[:size]) _, err = io.ReadFull(r.Reader, buf[:size])
if err != nil { if err != nil {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
return 0, err return 0, err
} }
@ -107,7 +107,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
realLen := size - r.Overhead() realLen := size - r.Overhead()
n := copy(b, buf[:realLen]) n := copy(b, buf[:realLen])
if len(b) >= realLen { if len(b) >= realLen {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
return n, nil return n, nil
} }

View File

@ -34,7 +34,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
n := copy(b, cr.buf[cr.offset:]) n := copy(b, cr.buf[cr.offset:])
cr.offset += n cr.offset += n
if cr.offset == len(cr.buf) { if cr.offset == len(cr.buf) {
pool.BufPool.Put(cr.buf[:cap(cr.buf)]) pool.Put(cr.buf)
cr.buf = nil cr.buf = nil
} }
return n, nil return n, nil
@ -59,15 +59,15 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
return size, nil return size, nil
} }
buf := pool.BufPool.Get().([]byte) buf := pool.Get(size)
_, err = io.ReadFull(cr.Reader, buf[:size]) _, err = io.ReadFull(cr.Reader, buf)
if err != nil { if err != nil {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
return 0, err return 0, err
} }
n := copy(b, cr.buf[:]) n := copy(b, buf)
cr.offset = n cr.offset = n
cr.buf = buf[:size] cr.buf = buf
return n, nil return n, nil
} }
@ -76,8 +76,8 @@ type chunkWriter struct {
} }
func (cw *chunkWriter) Write(b []byte) (n int, err error) { func (cw *chunkWriter) Write(b []byte) (n int, err error) {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
defer pool.BufPool.Put(buf[:cap(buf)]) defer pool.Put(buf)
length := len(b) length := len(b)
for { for {
if length == 0 { if length == 0 {

76
component/vmess/http.go Normal file
View File

@ -0,0 +1,76 @@
package vmess
import (
"bytes"
"fmt"
"math/rand"
"net"
"net/http"
"net/textproto"
)
type httpConn struct {
net.Conn
cfg *HTTPConfig
rhandshake bool
whandshake bool
}
type HTTPConfig struct {
Method string
Host string
Path []string
Headers map[string][]string
}
// Read implements net.Conn.Read()
func (hc *httpConn) Read(b []byte) (int, error) {
if hc.rhandshake {
n, err := hc.Conn.Read(b)
return n, err
}
reader := textproto.NewConn(hc.Conn)
// First line: GET /index.html HTTP/1.0
if _, err := reader.ReadLine(); err != nil {
return 0, err
}
if _, err := reader.ReadMIMEHeader(); err != nil {
return 0, err
}
hc.rhandshake = true
return hc.Conn.Read(b)
}
// Write implements io.Writer.
func (hc *httpConn) Write(b []byte) (int, error) {
if hc.whandshake {
return hc.Conn.Write(b)
}
path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))]
u := fmt.Sprintf("http://%s%s", hc.cfg.Host, path)
req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b))
for key, list := range hc.cfg.Headers {
req.Header.Set(key, list[rand.Intn(len(list))])
}
req.ContentLength = int64(len(b))
if err := req.Write(hc.Conn); err != nil {
return 0, err
}
hc.whandshake = true
return len(b), nil
}
func (hc *httpConn) Close() error {
return hc.Conn.Close()
}
func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn {
return &httpConn{
Conn: conn,
cfg: cfg,
}
}

24
component/vmess/tls.go Normal file
View File

@ -0,0 +1,24 @@
package vmess
import (
"crypto/tls"
"net"
)
type TLSConfig struct {
Host string
SkipCertVerify bool
SessionCache tls.ClientSessionCache
}
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
tlsConfig := &tls.Config{
ServerName: cfg.Host,
InsecureSkipVerify: cfg.SkipCertVerify,
ClientSessionCache: cfg.SessionCache,
}
tlsConn := tls.Client(conn, tlsConfig)
err := tlsConn.Handshake()
return tlsConn, err
}

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
"net/http"
"runtime" "runtime"
"sync" "sync"
@ -69,10 +68,6 @@ type Client struct {
user []*ID user []*ID
uuid *uuid.UUID uuid *uuid.UUID
security Security security Security
tls bool
host string
wsConfig *WebsocketConfig
tlsConfig *tls.Config
} }
// Config of vmess // Config of vmess
@ -80,28 +75,13 @@ type Config struct {
UUID string UUID string
AlterID uint16 AlterID uint16
Security string Security string
TLS bool
HostName string
Port string Port string
NetWork string HostName string
WebSocketPath string
WebSocketHeaders map[string]string
SkipCertVerify bool
SessionCache tls.ClientSessionCache
} }
// New return a Conn with net.Conn and DstAddr // StreamConn return a Conn with net.Conn and DstAddr
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error
r := rand.Intn(len(c.user)) r := rand.Intn(len(c.user))
if c.wsConfig != nil {
conn, err = NewWebsocketConn(conn, c.wsConfig)
if err != nil {
return nil, err
}
} else if c.tls {
conn = tls.Client(conn, c.tlsConfig)
}
return newConn(conn, c.user[r], dst, c.security) return newConn(conn, c.user[r], dst, c.security)
} }
@ -129,57 +109,9 @@ func NewClient(config Config) (*Client, error) {
return nil, fmt.Errorf("Unknown security type: %s", config.Security) return nil, fmt.Errorf("Unknown security type: %s", config.Security)
} }
if config.NetWork != "" && config.NetWork != "ws" {
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
}
header := http.Header{}
for k, v := range config.WebSocketHeaders {
header.Add(k, v)
}
host := net.JoinHostPort(config.HostName, config.Port)
var tlsConfig *tls.Config
if config.TLS {
tlsConfig = &tls.Config{
ServerName: config.HostName,
InsecureSkipVerify: config.SkipCertVerify,
ClientSessionCache: config.SessionCache,
}
if tlsConfig.ClientSessionCache == nil {
tlsConfig.ClientSessionCache = getClientSessionCache()
}
if host := header.Get("Host"); host != "" {
tlsConfig.ServerName = host
}
}
var wsConfig *WebsocketConfig
if config.NetWork == "ws" {
wsConfig = &WebsocketConfig{
Host: host,
Path: config.WebSocketPath,
Headers: header,
TLS: config.TLS,
TLSConfig: tlsConfig,
}
}
return &Client{ return &Client{
user: newAlterIDs(newID(&uid), config.AlterID), user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid, uuid: &uid,
security: security, security: security,
tls: config.TLS,
host: host,
wsConfig: wsConfig,
tlsConfig: tlsConfig,
}, nil }, nil
} }
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
clientSessionCache = tls.NewLRUClientSessionCache(128)
})
return clientSessionCache
}

View File

@ -26,10 +26,12 @@ type websocketConn struct {
type WebsocketConfig struct { type WebsocketConfig struct {
Host string Host string
Port string
Path string Path string
Headers http.Header Headers http.Header
TLS bool TLS bool
TLSConfig *tls.Config SkipCertVerify bool
SessionCache tls.ClientSessionCache
} }
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
@ -111,7 +113,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
return wsc.conn.SetWriteDeadline(t) return wsc.conn.SetWriteDeadline(t)
} }
func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
dialer := &websocket.Dialer{ dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) { NetDial: func(network, addr string) (net.Conn, error) {
return conn, nil return conn, nil
@ -124,17 +126,20 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
scheme := "ws" scheme := "ws"
if c.TLS { if c.TLS {
scheme = "wss" scheme = "wss"
dialer.TLSClientConfig = c.TLSConfig dialer.TLSClientConfig = &tls.Config{
ServerName: c.Host,
InsecureSkipVerify: c.SkipCertVerify,
ClientSessionCache: c.SessionCache,
} }
host, port, _ := net.SplitHostPort(c.Host) if host := c.Headers.Get("Host"); host != "" {
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { dialer.TLSClientConfig.ServerName = host
host = c.Host }
} }
uri := url.URL{ uri := url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: net.JoinHostPort(c.Host, c.Port),
Path: c.Path, Path: c.Path,
} }
@ -151,7 +156,7 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
if resp != nil { if resp != nil {
reason = resp.Status reason = resp.Status
} }
return nil, fmt.Errorf("Dial %s error: %s", host, reason) return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason)
} }
return &websocketConn{ return &websocketConn{

View File

@ -106,13 +106,19 @@ type RawConfig struct {
ExternalUI string `yaml:"external-ui"` ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"` ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
Hosts map[string]string `yaml:"hosts"` Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"` DNS RawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"` Experimental Experimental `yaml:"experimental"`
Proxy []map[string]interface{} `yaml:"Proxy"` Proxy []map[string]interface{} `yaml:"proxies"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
Rule []string `yaml:"Rule"` Rule []string `yaml:"rules"`
// remove after 1.0
ProxyProviderOld map[string]map[string]interface{} `yaml:"proxy-provider"`
ProxyOld []map[string]interface{} `yaml:"Proxy"`
ProxyGroupOld []map[string]interface{} `yaml:"Proxy Group"`
RuleOld []string `yaml:"Rule"`
} }
// Parse config // Parse config
@ -152,6 +158,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"8.8.8.8", "8.8.8.8",
}, },
}, },
// remove after 1.0
RuleOld: []string{},
ProxyOld: []map[string]interface{}{},
ProxyGroupOld: []map[string]interface{}{},
} }
if err := yaml.Unmarshal(buf, &rawCfg); err != nil { if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
@ -245,14 +256,17 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
defer func() { if len(proxiesConfig) == 0 {
// Destroy already created provider when err != nil proxiesConfig = cfg.ProxyOld
if err != nil {
for _, provider := range providersMap {
provider.Destroy()
} }
if len(groupsConfig) == 0 {
groupsConfig = cfg.ProxyGroupOld
}
if len(providersConfig) == 0 {
providersConfig = cfg.ProxyProviderOld
} }
}()
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
@ -272,7 +286,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
proxyList = append(proxyList, proxy.Name()) proxyList = append(proxyList, proxy.Name())
} }
// keep the origional order of ProxyGroups in config file // keep the original order of ProxyGroups in config file
for idx, mapping := range groupsConfig { for idx, mapping := range groupsConfig {
groupName, existName := mapping["name"].(string) groupName, existName := mapping["name"].(string)
if !existName { if !existName {
@ -351,6 +365,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
rules := []C.Rule{} rules := []C.Rule{}
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
// remove after 1.0
if len(rulesConfig) == 0 {
rulesConfig = cfg.RuleOld
}
// parse rules // parse rules
for idx, line := range rulesConfig { for idx, line := range rulesConfig {
rule := trimArr(strings.Split(line, ",")) rule := trimArr(strings.Split(line, ","))

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
@ -27,6 +28,28 @@ func downloadMMDB(path string) (err error) {
return err return err
} }
func initMMDB() error {
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download")
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("Can't download MMDB: %s", err.Error())
}
}
if !mmdb.Verify() {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error())
}
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("Can't download MMDB: %s", err.Error())
}
}
return nil
}
// Init prepare necessary files // Init prepare necessary files
func Init(dir string) error { func Init(dir string) error {
// initial homedir // initial homedir
@ -48,11 +71,8 @@ func Init(dir string) error {
} }
// initial mmdb // initial mmdb
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { if err := initMMDB(); err != nil {
log.Infoln("Can't find MMDB, start download") return fmt.Errorf("Can't initial MMDB: %w", err)
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("Can't download MMDB: %s", err.Error())
}
} }
return nil return nil
} }

View File

@ -10,15 +10,19 @@ import (
// Adapter Type // Adapter Type
const ( const (
Direct AdapterType = iota Direct AdapterType = iota
Fallback
Reject Reject
Selector
Shadowsocks Shadowsocks
Snell Snell
Socks5 Socks5
Http Http
URLTest
Vmess Vmess
Trojan
Relay
Selector
Fallback
URLTest
LoadBalance LoadBalance
) )
@ -59,10 +63,14 @@ type PacketConn interface {
type ProxyAdapter interface { type ProxyAdapter interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
DialUDP(metadata *Metadata) (PacketConn, error) DialUDP(metadata *Metadata) (PacketConn, error)
SupportUDP() bool SupportUDP() bool
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
Addr() string
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
Unwrap(metadata *Metadata) Proxy
} }
type DelayHistory struct { type DelayHistory struct {
@ -86,12 +94,9 @@ func (at AdapterType) String() string {
switch at { switch at {
case Direct: case Direct:
return "Direct" return "Direct"
case Fallback:
return "Fallback"
case Reject: case Reject:
return "Reject" return "Reject"
case Selector:
return "Selector"
case Shadowsocks: case Shadowsocks:
return "Shadowsocks" return "Shadowsocks"
case Snell: case Snell:
@ -100,12 +105,22 @@ func (at AdapterType) String() string {
return "Socks5" return "Socks5"
case Http: case Http:
return "Http" return "Http"
case URLTest:
return "URLTest"
case Vmess: case Vmess:
return "Vmess" return "Vmess"
case Trojan:
return "Trojan"
case Relay:
return "Relay"
case Selector:
return "Selector"
case Fallback:
return "Fallback"
case URLTest:
return "URLTest"
case LoadBalance: case LoadBalance:
return "LoadBalance" return "LoadBalance"
default: default:
return "Unknown" return "Unknown"
} }
@ -122,8 +137,8 @@ type UDPPacket interface {
// this is important when using Fake-IP. // this is important when using Fake-IP.
WriteBack(b []byte, addr net.Addr) (n int, err error) WriteBack(b []byte, addr net.Addr) (n int, err error)
// Close closes the underlaying connection. // Drop call after packet is used, could recycle buffer in this function.
Close() error Drop()
// LocalAddr returns the source IP/Port of packet // LocalAddr returns the source IP/Port of packet
LocalAddr() net.Addr LocalAddr() net.Addr

View File

@ -34,13 +34,19 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
} }
} }
d := dialer.Dialer() d, err := dialer.Dialer()
if err != nil {
return nil, err
}
if dialer.DialHook != nil { if dialer.DialHook != nil {
network := "udp" network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") { if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp" network = "tcp"
} }
dialer.DialHook(d, network, ip) if err := dialer.DialHook(d, network, ip); err != nil {
return nil, err
}
} }
c.Client.Dialer = d c.Client.Dialer = d

View File

@ -14,7 +14,7 @@ type geoipFilter struct{}
func (gf *geoipFilter) Match(ip net.IP) bool { func (gf *geoipFilter) Match(ip net.IP) bool {
record, _ := mmdb.Instance().Country(ip) record, _ := mmdb.Instance().Country(ip)
return record.Country.IsoCode == "CN" || record.Country.IsoCode == "" return record.Country.IsoCode != "CN" && record.Country.IsoCode != ""
} }
type ipnetFilter struct { type ipnetFilter struct {

View File

@ -18,7 +18,14 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
q := r.Question[0] q := r.Question[0]
if q.Qtype == D.TypeAAAA { if q.Qtype == D.TypeAAAA {
D.HandleFailed(w, r) msg := &D.Msg{}
msg.Answer = []D.RR{}
msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true
msg.RecursionAvailable = true
w.WriteMsg(msg)
return return
} else if q.Qtype != D.TypeA { } else if q.Qtype != D.TypeA {
next(w, r) next(w, r)
@ -39,8 +46,10 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
msg.Answer = []D.RR{rr} msg.Answer = []D.RR{rr}
setMsgTTL(msg, 1) setMsgTTL(msg, 1)
msg.SetRcode(r, msg.Rcode) msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true msg.Authoritative = true
msg.RecursionAvailable = true
w.WriteMsg(msg) w.WriteMsg(msg)
return return
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"math/rand" "math/rand"
"net" "net"
"strings" "strings"
@ -41,7 +42,7 @@ type Resolver struct {
fallback []dnsClient fallback []dnsClient
fallbackFilters []fallbackFilter fallbackFilters []fallbackFilter
group singleflight.Group group singleflight.Group
cache *cache.Cache lruCache *cache.LruCache
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA // ResolveIP request with TypeA and TypeAAAA, priority return TypeA
@ -95,31 +96,43 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
} }
q := m.Question[0] q := m.Question[0]
cache, expireTime := r.cache.GetWithExpire(q.String()) cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if cache != nil { if hit {
now := time.Now()
msg = cache.(*D.Msg).Copy() msg = cache.(*D.Msg).Copy()
if expireTime.Before(now) {
setMsgTTL(msg, uint32(1)) // Continue fetch
go r.exchangeWithoutCache(m)
} else {
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
}
return return
} }
return r.exchangeWithoutCache(m)
}
// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache
func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
q := m.Question[0]
defer func() { defer func() {
if msg == nil { if msg == nil {
return return
} }
putMsgToCache(r.cache, q.String(), msg) putMsgToCache(r.lruCache, q.String(), msg)
if r.mapping { if r.mapping {
ips := r.msgToIP(msg) ips := r.msgToIP(msg)
for _, ip := range ips { for _, ip := range ips {
putMsgToCache(r.cache, ip.String(), msg) putMsgToCache(r.lruCache, ip.String(), msg)
} }
} }
}() }()
ret, err, _ := r.group.Do(q.String(), func() (interface{}, error) { ret, err, shared := r.group.Do(q.String(), func() (interface{}, error) {
isIPReq := isIPRequest(q) isIPReq := isIPRequest(q)
if isIPReq { if isIPReq {
msg, err := r.fallbackExchange(m) return r.fallbackExchange(m)
return msg, err
} }
return r.batchExchange(r.main, m) return r.batchExchange(r.main, m)
@ -127,6 +140,9 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
if err == nil { if err == nil {
msg = ret.(*D.Msg) msg = ret.(*D.Msg)
if shared {
msg = msg.Copy()
}
} }
return return
@ -138,7 +154,7 @@ func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
return r.pool.LookBack(ip) return r.pool.LookBack(ip)
} }
cache := r.cache.Get(ip.String()) cache, _ := r.lruCache.Get(ip.String())
if cache == nil { if cache == nil {
return "", false return "", false
} }
@ -168,13 +184,23 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
for _, client := range clients { for _, client := range clients {
r := client r := client
fast.Go(func() (interface{}, error) { fast.Go(func() (interface{}, error) {
return r.ExchangeContext(ctx, m) m, err := r.ExchangeContext(ctx, m)
if err != nil {
return nil, err
} else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused {
return nil, errors.New("server failure")
}
return m, nil
}) })
} }
elm := fast.Wait() elm := fast.Wait()
if elm == nil { if elm == nil {
return nil, errors.New("All DNS requests failed") err := errors.New("All DNS requests failed")
if fErr := fast.Error(); fErr != nil {
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
}
return nil, err
} }
msg = elm.(*D.Msg) msg = elm.(*D.Msg)
@ -192,9 +218,9 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
res := <-msgCh res := <-msgCh
if res.Error == nil { if res.Error == nil {
if ips := r.msgToIP(res.Msg); len(ips) != 0 { if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if r.shouldFallback(ips[0]) { if !r.shouldFallback(ips[0]) {
go func() { <-fallbackMsg }()
msg = res.Msg msg = res.Msg
err = res.Error
return msg, err return msg, err
} }
} }
@ -252,7 +278,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
} }
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result) ch := make(chan *result, 1)
go func() { go func() {
res, err := r.batchExchange(client, msg) res, err := r.batchExchange(client, msg)
ch <- &result{Msg: res, Error: err} ch <- &result{Msg: res, Error: err}
@ -282,13 +308,13 @@ type Config struct {
func New(config Config) *Resolver { func New(config Config) *Resolver {
defaultResolver := &Resolver{ defaultResolver := &Resolver{
main: transform(config.Default, nil), main: transform(config.Default, nil),
cache: cache.New(time.Second * 60), lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
} }
r := &Resolver{ r := &Resolver{
ipv6: config.IPv6, ipv6: config.IPv6,
main: transform(config.Main, defaultResolver), main: transform(config.Main, defaultResolver),
cache: cache.New(time.Second * 60), lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
mapping: config.EnhancedMode == MAPPING, mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP, fakeip: config.EnhancedMode == FAKEIP,
pool: config.Pool, pool: config.Pool,

View File

@ -3,6 +3,8 @@ package dns
import ( import (
"net" "net"
"github.com/Dreamacro/clash/common/sockopt"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -58,6 +60,11 @@ func ReCreateServer(addr string, resolver *Resolver) error {
return err return err
} }
err = sockopt.UDPReuseaddr(p)
if err != nil {
return err
}
address = addr address = addr
handler := newHandler(resolver) handler := newHandler(resolver)
server = &Server{handler: handler} server = &Server{handler: handler}

View File

@ -11,7 +11,6 @@ import (
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
yaml "gopkg.in/yaml.v2"
) )
var ( var (
@ -46,8 +45,8 @@ func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
} }
// MarshalYAML serialize EnhancedMode with yaml // MarshalYAML serialize EnhancedMode with yaml
func (e EnhancedMode) MarshalYAML() ([]byte, error) { func (e EnhancedMode) MarshalYAML() (interface{}, error) {
return yaml.Marshal(e.String()) return e.String(), nil
} }
// UnmarshalJSON unserialize EnhancedMode with json // UnmarshalJSON unserialize EnhancedMode with json
@ -80,21 +79,21 @@ func (e EnhancedMode) String() string {
} }
} }
func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
var ttl time.Duration var ttl uint32
switch { switch {
case len(msg.Answer) != 0: case len(msg.Answer) != 0:
ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second ttl = msg.Answer[0].Header().Ttl
case len(msg.Ns) != 0: case len(msg.Ns) != 0:
ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second ttl = msg.Ns[0].Header().Ttl
case len(msg.Extra) != 0: case len(msg.Extra) != 0:
ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second ttl = msg.Extra[0].Header().Ttl
default: default:
log.Debugln("[DNS] response msg error: %#v", msg) log.Debugln("[DNS] response msg error: %#v", msg)
return return
} }
c.Put(key, msg.Copy(), ttl) c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
} }
func setMsgTTL(msg *D.Msg, ttl uint32) { func setMsgTTL(msg *D.Msg, ttl uint32) {
@ -134,6 +133,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ClientSessionCache: globalSessionCache, ClientSessionCache: globalSessionCache,
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
NextProtos: []string{"dns"}, NextProtos: []string{"dns"},
ServerName: host,
}, },
UDPSize: 4096, UDPSize: 4096,
Timeout: 5 * time.Second, Timeout: 5 * time.Second,

22
go.mod
View File

@ -1,22 +1,22 @@
module github.com/Dreamacro/clash module github.com/Dreamacro/clash
go 1.13 go 1.14
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/Dreamacro/go-shadowsocks2 v0.1.5
github.com/eapache/queue v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.0.3+incompatible github.com/go-chi/chi v4.1.1+incompatible
github.com/go-chi/cors v1.0.0 github.com/go-chi/cors v1.1.1
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.2.0+incompatible github.com/gofrs/uuid v3.3.0+incompatible
github.com/gorilla/websocket v1.4.1 github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.27 github.com/miekg/dns v1.1.29
github.com/oschwald/geoip2-golang v1.4.0 github.com/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/eapache/channels.v1 v1.1.0
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
) )

57
go.sum
View File

@ -5,62 +5,54 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg= golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -68,7 +60,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -158,13 +158,6 @@ func updateHosts(tree *trie.Trie) {
} }
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
oldProviders := tunnel.Providers()
// close providers goroutine
for _, provider := range oldProviders {
provider.Destroy()
}
tunnel.UpdateProxies(proxies, providers) tunnel.UpdateProxies(proxies, providers)
} }

View File

@ -3,15 +3,40 @@ package hub
import ( import (
"github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/hub/route" "github.com/Dreamacro/clash/hub/route"
"github.com/Dreamacro/clash/config"
) )
type Option func(*config.Config)
func WithExternalUI(externalUI string) Option {
return func(cfg *config.Config) {
cfg.General.ExternalUI = externalUI
}
}
func WithExternalController(externalController string) Option {
return func(cfg *config.Config) {
cfg.General.ExternalController = externalController
}
}
func WithSecret(secret string) Option {
return func(cfg *config.Config) {
cfg.General.Secret = secret
}
}
// Parse call at the beginning of clash // Parse call at the beginning of clash
func Parse() error { func Parse(options ...Option) error {
cfg, err := executor.Parse() cfg, err := executor.Parse()
if err != nil { if err != nil {
return err return err
} }
for _, option := range options {
option(cfg)
}
if cfg.General.ExternalUI != "" { if cfg.General.ExternalUI != "" {
route.SetUIPath(cfg.General.ExternalUI) route.SetUIPath(cfg.General.ExternalUI)
} }

View File

@ -110,9 +110,9 @@ func authentication(next http.Handler) http.Handler {
header := r.Header.Get("Authorization") header := r.Header.Get("Authorization")
text := strings.SplitN(header, " ", 2) text := strings.SplitN(header, " ", 2)
hasUnvalidHeader := text[0] != "Bearer" hasInvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret hasInvalidSecret := len(text) != 2 || text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret { if hasInvalidHeader || hasInvalidSecret {
render.Status(r, http.StatusUnauthorized) render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized) render.JSON(w, r, ErrUnauthorized)
return return

View File

@ -55,6 +55,11 @@ func (l LogLevel) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String()) return json.Marshal(l.String())
} }
// MarshalYAML serialize LogLevel with yaml
func (l LogLevel) MarshalYAML() (interface{}, error) {
return l.String(), nil
}
func (l LogLevel) String() string { func (l LogLevel) String() string {
switch l { switch l {
case INFO: case INFO:

39
main.go
View File

@ -10,22 +10,38 @@ import (
"syscall" "syscall"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
var ( var (
flagset map[string]bool
version bool version bool
testConfig bool
homeDir string homeDir string
configFile string configFile string
externalUI string
externalController string
secret string
) )
func init() { func init() {
flag.StringVar(&homeDir, "d", "", "set configuration directory") flag.StringVar(&homeDir, "d", "", "set configuration directory")
flag.StringVar(&configFile, "f", "", "specify configuration file") flag.StringVar(&configFile, "f", "", "specify configuration file")
flag.StringVar(&externalUI, "ext-ui", "", "override external ui directory")
flag.StringVar(&externalController, "ext-ctl", "", "override external controller address")
flag.StringVar(&secret, "secret", "", "override secret for RESTful API")
flag.BoolVar(&version, "v", false, "show current version of clash") flag.BoolVar(&version, "v", false, "show current version of clash")
flag.BoolVar(&testConfig, "t", false, "test configuration and exit")
flag.Parse() flag.Parse()
flagset = map[string]bool{}
flag.Visit(func(f *flag.Flag) {
flagset[f.Name] = true
})
} }
func main() { func main() {
@ -57,7 +73,28 @@ func main() {
log.Fatalln("Initial configuration directory error: %s", err.Error()) log.Fatalln("Initial configuration directory error: %s", err.Error())
} }
if err := hub.Parse(); err != nil { if testConfig {
if _, err := executor.Parse(); err != nil {
log.Errorln(err.Error())
fmt.Printf("configuration file %s test failed\n", constant.Path.Config())
os.Exit(1)
}
fmt.Printf("configuration file %s test is successful\n", constant.Path.Config())
return
}
var options []hub.Option
if flagset["ext-ui"] {
options = append(options, hub.WithExternalUI(externalUI))
}
if flagset["ext-ctl"] {
options = append(options, hub.WithExternalController(externalController))
}
if flagset["secret"] {
options = append(options, hub.WithSecret(secret))
}
if err := hub.Parse(options...); err != nil {
log.Fatalln("Parse config error: %s", err.Error()) log.Fatalln("Parse config error: %s", err.Error())
} }

View File

@ -5,6 +5,7 @@ import (
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/redir"
"github.com/Dreamacro/clash/proxy/socks" "github.com/Dreamacro/clash/proxy/socks"
@ -18,6 +19,7 @@ var (
socksUDPListener *socks.SockUDPListener socksUDPListener *socks.SockUDPListener
httpListener *http.HttpListener httpListener *http.HttpListener
redirListener *redir.RedirListener redirListener *redir.RedirListener
redirUDPListener *redir.RedirUDPListener
) )
type listener interface { type listener interface {
@ -131,6 +133,14 @@ func ReCreateRedir(port int) error {
redirListener = nil redirListener = nil
} }
if redirUDPListener != nil {
if redirUDPListener.Address() == addr {
return nil
}
redirUDPListener.Close()
redirUDPListener = nil
}
if portIsZero(addr) { if portIsZero(addr) {
return nil return nil
} }
@ -141,6 +151,11 @@ func ReCreateRedir(port int) error {
return err return err
} }
redirUDPListener, err = redir.NewRedirUDPProxy(addr)
if err != nil {
log.Warnln("Failed to start Redir UDP Listener: %s", err)
}
return nil return nil
} }

View File

@ -55,5 +55,5 @@ func handleRedir(conn net.Conn) {
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) conn.(*net.TCPConn).SetKeepAlive(true)
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP)) tunnel.Add(inbound.NewSocket(target, conn, C.REDIR))
} }

74
proxy/redir/udp.go Normal file
View File

@ -0,0 +1,74 @@
package redir
import (
"net"
adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
)
type RedirUDPListener struct {
net.PacketConn
address string
closed bool
}
func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
l, err := net.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
rl := &RedirUDPListener{l, addr, false}
c := l.(*net.UDPConn)
err = setsockopt(c, addr)
if err != nil {
return nil, err
}
go func() {
oob := make([]byte, 1024)
for {
buf := pool.Get(pool.RelayBufferSize)
n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob)
if err != nil {
pool.Put(buf)
if rl.closed {
break
}
continue
}
rAddr, err := getOrigDst(oob, oobn)
if err != nil {
continue
}
handleRedirUDP(l, buf[:n], lAddr, rAddr)
}
}()
return rl, nil
}
func (l *RedirUDPListener) Close() error {
l.closed = true
return l.PacketConn.Close()
}
func (l *RedirUDPListener) Address() string {
return l.address
}
func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
target := socks5.ParseAddrToSocksAddr(rAddr)
pkt := &packet{
lAddr: lAddr,
buf: buf,
}
tunnel.AddPacket(adapters.NewPacket(target, pkt, C.REDIR))
}

73
proxy/redir/udp_linux.go Normal file
View File

@ -0,0 +1,73 @@
// +build linux
package redir
import (
"encoding/binary"
"errors"
"net"
"syscall"
)
const (
IPV6_TRANSPARENT = 0x4b
IPV6_RECVORIGDSTADDR = 0x4a
)
func setsockopt(c *net.UDPConn, addr string) error {
isIPv6 := true
host, _, err := net.SplitHostPort(addr)
if err != nil {
return err
}
ip := net.ParseIP(host)
if ip != nil && ip.To4() != nil {
isIPv6 = false
}
rc, err := c.SyscallConn()
if err != nil {
return err
}
rc.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
}
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
}
})
return err
}
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return nil, err
}
for _, msg := range msgs {
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
ip := net.IP(msg.Data[4:8])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
ip := net.IP(msg.Data[8:24])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
}
}
return nil, errors.New("cannot find origDst")
}

16
proxy/redir/udp_other.go Normal file
View File

@ -0,0 +1,16 @@
// +build !linux
package redir
import (
"errors"
"net"
)
func setsockopt(c *net.UDPConn, addr string) error {
return errors.New("UDP redir not supported on current platform")
}
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
return nil, errors.New("UDP redir not supported on current platform")
}

38
proxy/redir/utils.go Normal file
View File

@ -0,0 +1,38 @@
package redir
import (
"net"
"github.com/Dreamacro/clash/common/pool"
)
type packet struct {
lAddr *net.UDPAddr
buf []byte
}
func (c *packet) Data() []byte {
return c.buf
}
// WriteBack opens a new socket binding `addr` to wirte UDP packet back
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr)
if err != nil {
n = 0
return
}
n, err = tc.Write(b)
tc.Close()
return
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr {
return c.lAddr
}
func (c *packet) Drop() {
pool.Put(c.buf)
return
}

View File

@ -0,0 +1,96 @@
// +build linux
package redir
import (
"fmt"
"net"
"os"
"strconv"
"syscall"
)
// dialUDP acts like net.DialUDP for transparent proxy.
// It binds to a non-local address(`lAddr`).
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
rSockAddr, err := udpAddrToSockAddr(rAddr)
if err != nil {
return nil, err
}
lSockAddr, err := udpAddrToSockAddr(lAddr)
if err != nil {
return nil, err
}
fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0)
if err != nil {
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Bind(fd, lSockAddr); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Connect(fd, rSockAddr); err != nil {
syscall.Close(fd)
return nil, err
}
fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String()))
defer fdFile.Close()
c, err := net.FileConn(fdFile)
if err != nil {
syscall.Close(fd)
return nil, err
}
return c.(*net.UDPConn), nil
}
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
switch {
case addr.IP.To4() != nil:
ip := [4]byte{}
copy(ip[:], addr.IP.To4())
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
default:
ip := [16]byte{}
copy(ip[:], addr.IP.To16())
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
if err != nil {
zoneID = 0
}
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
}
}
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
switch net[len(net)-1] {
case '4':
return syscall.AF_INET
case '6':
return syscall.AF_INET6
}
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
return syscall.AF_INET
}
return syscall.AF_INET6
}

View File

@ -0,0 +1,12 @@
// +build !linux
package redir
import (
"errors"
"net"
)
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
return nil, errors.New("UDP redir not supported on current platform")
}

View File

@ -64,5 +64,5 @@ func handleSocks(conn net.Conn) {
io.Copy(ioutil.Discard, conn) io.Copy(ioutil.Discard, conn)
return return
} }
tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS))
} }

View File

@ -5,6 +5,7 @@ import (
adapters "github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/sockopt"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
@ -22,13 +23,18 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
return nil, err return nil, err
} }
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
if err != nil {
return nil, err
}
sl := &SockUDPListener{l, addr, false} sl := &SockUDPListener{l, addr, false}
go func() { go func() {
for { for {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
n, remoteAddr, err := l.ReadFrom(buf) n, remoteAddr, err := l.ReadFrom(buf)
if err != nil { if err != nil {
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
if sl.closed { if sl.closed {
break break
} }
@ -54,11 +60,11 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
target, payload, err := socks5.DecodeUDPPacket(buf) target, payload, err := socks5.DecodeUDPPacket(buf)
if err != nil { if err != nil {
// Unresolved UDP packet, return buffer to the pool // Unresolved UDP packet, return buffer to the pool
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
return return
} }
packet := &fakeConn{ packet := &packet{
PacketConn: pc, pc: pc,
rAddr: addr, rAddr: addr,
payload: payload, payload: payload,
bufRef: buf, bufRef: buf,

View File

@ -7,33 +7,32 @@ import (
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/component/socks5"
) )
type fakeConn struct { type packet struct {
net.PacketConn pc net.PacketConn
rAddr net.Addr rAddr net.Addr
payload []byte payload []byte
bufRef []byte bufRef []byte
} }
func (c *fakeConn) Data() []byte { func (c *packet) Data() []byte {
return c.payload return c.payload
} }
// WriteBack wirtes UDP packet with source(ip, port) = `addr` // WriteBack wirtes UDP packet with source(ip, port) = `addr`
func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil { if err != nil {
return return
} }
return c.PacketConn.WriteTo(packet, c.rAddr) return c.pc.WriteTo(packet, c.rAddr)
} }
// LocalAddr returns the source IP/Port of UDP Packet // LocalAddr returns the source IP/Port of UDP Packet
func (c *fakeConn) LocalAddr() net.Addr { func (c *packet) LocalAddr() net.Addr {
return c.rAddr return c.rAddr
} }
func (c *fakeConn) Close() error { func (c *packet) Drop() {
err := c.PacketConn.Close() pool.Put(c.bufRef)
pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) return
return err
} }

View File

@ -18,8 +18,8 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
req := request.R req := request.R
host := req.Host host := req.Host
inboundReeder := bufio.NewReader(request) inboundReader := bufio.NewReader(request)
outboundReeder := bufio.NewReader(outbound) outboundReader := bufio.NewReader(outbound)
for { for {
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
@ -33,7 +33,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
} }
handleResponse: handleResponse:
resp, err := http.ReadResponse(outboundReeder, req) resp, err := http.ReadResponse(outboundReader, req)
if err != nil { if err != nil {
break break
} }
@ -61,14 +61,14 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
} }
// even if resp.Write write body to the connection, but some http request have to Copy to close it // even if resp.Write write body to the connection, but some http request have to Copy to close it
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
_, err = io.CopyBuffer(request, resp.Body, buf) _, err = io.CopyBuffer(request, resp.Body, buf)
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
break break
} }
req, err = http.ReadRequest(inboundReeder) req, err = http.ReadRequest(inboundReader)
if err != nil { if err != nil {
break break
} }
@ -82,15 +82,16 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
} }
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) { func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
defer packet.Drop()
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil { if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
return return
} }
DefaultManager.Upload() <- int64(len(packet.Data()))
} }
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
defer pool.BufPool.Put(buf[:cap(buf)]) defer pool.Put(buf)
defer natTable.Delete(key) defer natTable.Delete(key)
defer pc.Close() defer pc.Close()
@ -101,11 +102,14 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) {
return return
} }
if fAddr != nil {
from = fAddr
}
n, err = packet.WriteBack(buf[:n], from) n, err = packet.WriteBack(buf[:n], from)
if err != nil { if err != nil {
return return
} }
DefaultManager.Download() <- int64(n)
} }
} }
@ -118,16 +122,16 @@ func relay(leftConn, rightConn net.Conn) {
ch := make(chan error) ch := make(chan error)
go func() { go func() {
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
_, err := io.CopyBuffer(leftConn, rightConn, buf) _, err := io.CopyBuffer(leftConn, rightConn, buf)
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
leftConn.SetReadDeadline(time.Now()) leftConn.SetReadDeadline(time.Now())
ch <- err ch <- err
}() }()
buf := pool.BufPool.Get().([]byte) buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(rightConn, leftConn, buf) io.CopyBuffer(rightConn, leftConn, buf)
pool.BufPool.Put(buf[:cap(buf)]) pool.Put(buf)
rightConn.SetReadDeadline(time.Now()) rightConn.SetReadDeadline(time.Now())
<-ch <-ch
} }

View File

@ -51,6 +51,11 @@ func (m TunnelMode) MarshalJSON() ([]byte, error) {
return json.Marshal(m.String()) return json.Marshal(m.String())
} }
// MarshalYAML serialize TunnelMode with yaml
func (m TunnelMode) MarshalYAML() (interface{}, error) {
return m.String(), nil
}
func (m TunnelMode) String() string { func (m TunnelMode) String() string {
switch m { switch m {
case Global: case Global:

View File

@ -103,6 +103,14 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
return n, err return n, err
} }
func (ut *udpTracker) WriteWithMetadata(p []byte, metadata *C.Metadata) (int, error) {
n, err := ut.PacketConn.WriteWithMetadata(p, metadata)
upload := int64(n)
ut.manager.Upload() <- upload
ut.UploadTotal += upload
return n, err
}
func (ut *udpTracker) Close() error { func (ut *udpTracker) Close() error {
ut.manager.Leave(ut) ut.manager.Leave(ut)
return ut.PacketConn.Close() return ut.PacketConn.Close()

View File

@ -147,6 +147,9 @@ func preHandleMetadata(metadata *C.Metadata) error {
metadata.AddrType = C.AtypDomainName metadata.AddrType = C.AtypDomainName
if enhancedMode.FakeIPEnabled() { if enhancedMode.FakeIPEnabled() {
metadata.DstIP = nil metadata.DstIP = nil
} else if node := resolver.DefaultHosts.Search(host); node != nil {
// redir-host should lookup the hosts
metadata.DstIP = node.Data.(net.IP)
} }
} else if enhancedMode.IsFakeIP(metadata.DstIP) { } else if enhancedMode.IsFakeIP(metadata.DstIP) {
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
@ -182,6 +185,12 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
return return
} }
// make a fAddr if requset ip is fakeip
var fAddr net.Addr
if enhancedMode != nil && enhancedMode.IsFakeIP(metadata.DstIP) {
fAddr = metadata.UDPAddr()
}
if err := preHandleMetadata(metadata); err != nil { if err := preHandleMetadata(metadata); err != nil {
log.Debugln("[Metadata PreHandle] error: %s", err) log.Debugln("[Metadata PreHandle] error: %s", err)
return return
@ -231,7 +240,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
natTable.Set(key, pc) natTable.Set(key, pc)
natTable.Delete(lockKey) natTable.Delete(lockKey)
wg.Done() wg.Done()
go handleUDPToLocal(packet.UDPPacket, pc, key) go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr)
} }
wg.Wait() wg.Wait()