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
uses: actions/setup-go@v1
with:
go-version: 1.13.x
go-version: 1.14.x
- name: Check out code into the Go module directory
uses: actions/checkout@v1

108
README.md
View File

@ -19,23 +19,25 @@
## Features
- Local HTTP/HTTPS/SOCKS server
- GeoIP rule support
- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol
- Supports Netfilter TCP redirecting
- Comprehensive HTTP API
- Local HTTP/HTTPS/SOCKS server with/without authentication
- VMess, Shadowsocks, Trojan (experimental), Snell protocol support for remote connections. UDP is supported.
- Built-in DNS server that aims to minimize DNS pollution attacks, supports DoH/DoT upstream. Fake IP is also supported.
- Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
- Netfilter TCP redirecting. You can deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP API controller
## Install
Clash Requires Go >= 1.13. You can build it from source:
Clash requires Go >= 1.13. You can build it from source:
```sh
$ go get -u -v github.com/Dreamacro/clash
```
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)
Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available.
Check Clash version with:
@ -43,21 +45,17 @@ Check Clash version with:
$ 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), we can start the daemon this way:
In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way:
```sh
$ pm2 start clash
```
If you have Docker installed, you can run clash directly using `docker-compose`.
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
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)
## Config
@ -122,9 +120,10 @@ experimental:
# - "user2:pass2"
# # 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:
# '*.clash.dev': 127.0.0.1
# '.dev': 127.0.0.1
# 'alpha.clash.dev': '::1'
# dns:
@ -150,7 +149,7 @@ experimental:
# ipcidr: # ips in these subnets will be considered polluted
# - 240.0.0.0/4
Proxy:
proxies:
# shadowsocks
# The supported ciphers(encrypt methods):
# aes-128-gcm aes-192-gcm aes-256-gcm
@ -212,6 +211,24 @@ Proxy:
# ws-headers:
# 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
- name: "socks"
type: socks5
@ -243,7 +260,30 @@ Proxy:
# mode: http # or tls
# 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.
- name: "auto"
type: url-test
@ -284,7 +324,33 @@ Proxy Group:
- vmess1
- 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-KEYWORD,google,auto
- DOMAIN,google.com,auto
@ -308,7 +374,7 @@ Rule:
## Documentations
https://clash.gitbook.io/
## Thanks
## Credits
[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
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.NetWork = netType
metadata.NetWork = C.TCP
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip

View File

@ -18,6 +18,7 @@ var (
type Base struct {
name string
addr string
tp C.AdapterType
udp bool
}
@ -30,6 +31,10 @@ func (b *Base) Type() C.AdapterType {
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) {
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 {
return &Base{name, tp, udp}
func (b *Base) Addr() string {
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 {
@ -61,7 +74,7 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
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()}}
}
@ -194,7 +207,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
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)
if err != nil {
return

View File

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

View File

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

View File

@ -39,7 +39,12 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
}
proxy = NewHttp(*httpOption)
case "vmess":
vmessOption := &VmessOption{}
vmessOption := &VmessOption{
HTTPOpts: HTTPOptions{
Method: "GET",
Path: []string{"/"},
},
}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
@ -52,6 +57,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
break
}
proxy, err = NewSnell(*snellOption)
case "trojan":
trojanOption := &TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
}
proxy, err = NewTrojan(*trojanOption)
default:
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) {
return newConn(&NopConn{}, r), nil
return NewConn(&NopConn{}, r), nil
}
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
@ -16,7 +17,6 @@ import (
type Socks5 struct {
*Base
addr string
user string
pass string
tls bool
@ -35,19 +35,16 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err == nil && ss.tls {
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake()
err := cc.Handshake()
c = cc
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
}
var user *socks5.User
if ss.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 {
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) {
@ -126,10 +138,10 @@ func NewSocks5(option Socks5Option) *Socks5 {
return &Socks5{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5,
udp: option.UDP,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,
pass: option.Password,
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) {
n, a, e := uc.PacketConn.ReadFrom(b)
n, _, e := uc.PacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
}
@ -169,10 +181,15 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
if err != nil {
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
addrLength := len(addr)
copy(b, payload)
return n - addrLength - 3, a, nil
return n - len(addr) - 3, udpAddr, nil
}
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"
"fmt"
"net"
"net/http"
"strconv"
"strings"
@ -16,8 +17,8 @@ import (
type Vmess struct {
*Base
server string
client *vmess.Client
option *VmessOption
}
type VmessOption struct {
@ -30,19 +31,82 @@ type VmessOption struct {
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", v.server)
type HTTPOptions struct {
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 {
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)
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) {
@ -57,12 +121,12 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", v.server)
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
return nil, fmt.Errorf("%s connect error", v.addr)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
c, err = v.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
@ -75,14 +139,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
UUID: option.UUID,
AlterID: uint16(option.AlterID),
Security: security,
TLS: option.TLS,
HostName: option.Server,
Port: strconv.Itoa(option.Port),
NetWork: option.Network,
WebSocketPath: option.WSPath,
WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify,
SessionCache: getClientSessionCache(),
})
if err != nil {
return nil, err
@ -91,11 +149,12 @@ func NewVmess(option VmessOption) (*Vmess, error) {
return &Vmess{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess,
udp: true,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client,
option: &option,
}, 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 {
elm, _, _ := f.single.Do(func() (interface{}, error) {
return getProvidersProxies(f.providers), nil
@ -77,7 +82,7 @@ func (f *Fallback) findAliveProxy() C.Proxy {
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
Base: outbound.NewBase(name, C.Fallback, false),
Base: outbound.NewBase(name, "", C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
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))))
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() {
proxy := lb.Unwrap(metadata)
c, err = proxy.DialContext(ctx, metadata)
return
}
}
c, err = proxies[0].DialContext(ctx, metadata)
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
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))))
proxies := lb.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)
proxy := proxies[idx]
if proxy.Alive() {
return proxy.DialUDP(metadata)
return proxy
}
}
return proxies[0].DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
return proxies[0]
}
func (lb *LoadBalance) proxies() []C.Proxy {
@ -120,7 +117,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
return &LoadBalance{
Base: outbound.NewBase(name, C.LoadBalance, false),
Base: outbound.NewBase(name, "", C.LoadBalance, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
maxRetry: 3,
providers: providers,

View File

@ -64,7 +64,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
providers = append(providers, pd)
} else {
// select don't need health check
if groupOption.Type == "select" {
if groupOption.Type == "select" || groupOption.Type == "relay" {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
@ -108,6 +108,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
group = NewFallback(groupName, providers)
case "load-balance":
group = NewLoadBalance(groupName, providers)
case "relay":
group = NewRelay(groupName, providers)
default:
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 {
*outbound.Base
single *singledo.Single
selected C.Proxy
selected string
providers []provider.ProxyProvider
}
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 {
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) {
pc, err := s.selected.DialUDP(metadata)
pc, err := s.selectedProxy().DialUDP(metadata)
if err == nil {
pc.AppendToChains(s)
}
@ -35,12 +35,12 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
}
func (s *Selector) SupportUDP() bool {
return s.selected.SupportUDP()
return s.selectedProxy().SupportUDP()
}
func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range s.proxies() {
for _, proxy := range getProvidersProxies(s.providers) {
all = append(all, proxy.Name())
}
@ -52,13 +52,14 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
}
func (s *Selector) Now() string {
return s.selected.Name()
return s.selectedProxy().Name()
}
func (s *Selector) Set(name string) error {
for _, proxy := range s.proxies() {
for _, proxy := range getProvidersProxies(s.providers) {
if proxy.Name() == name {
s.selected = proxy
s.selected = name
s.single.Reset()
return nil
}
}
@ -66,18 +67,29 @@ func (s *Selector) Set(name string) error {
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) {
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 {
selected := providers[0].Proxies()[0]
selected := providers[0].Proxies()[0].Name()
return &Selector{
Base: outbound.NewBase(name, C.Selector, false),
Base: outbound.NewBase(name, "", C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
selected: selected,

View File

@ -38,6 +38,10 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err
}
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
return u.fast()
}
func (u *URLTest) proxies() []C.Proxy {
elm, _, _ := u.single.Do(func() (interface{}, error) {
return getProvidersProxies(u.providers), nil
@ -86,7 +90,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
return &URLTest{
Base: outbound.NewBase(name, C.URLTest, false),
Base: outbound.NewBase(name, "", C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10),
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
import (
"bytes"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"runtime"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"gopkg.in/yaml.v2"
)
const (
ReservedName = "default"
fileMode = 0666
)
// Provider Type
@ -49,8 +43,7 @@ type Provider interface {
VehicleType() VehicleType
Type() ProviderType
Initial() error
Reload() error
Destroy() error
Update() error
}
// ProxyProvider interface
@ -58,24 +51,24 @@ type ProxyProvider interface {
Provider
Proxies() []C.Proxy
HealthCheck()
Update() error
}
type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"`
}
// for auto gc
type ProxySetProvider struct {
name string
vehicle Vehicle
hash [16]byte
proxies []C.Proxy
healthCheck *HealthCheck
ticker *time.Ticker
updatedAt *time.Time
*proxySetProvider
}
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{}{
"name": pp.Name(),
"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
}
func (pp *ProxySetProvider) Reload() error {
return nil
}
func (pp *ProxySetProvider) HealthCheck() {
func (pp *proxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *ProxySetProvider) Update() error {
return pp.pull()
func (pp *proxySetProvider) Update() error {
elm, same, err := pp.fetcher.Update()
if err == nil && !same {
pp.onUpdate(elm)
}
return err
}
func (pp *ProxySetProvider) Destroy() error {
pp.healthCheck.close()
if pp.ticker != nil {
pp.ticker.Stop()
func (pp *proxySetProvider) Initial() error {
elm, err := pp.fetcher.Initial()
if err != nil {
return err
}
pp.onUpdate(elm)
return nil
}
func (pp *ProxySetProvider) Initial() error {
var buf []byte
var err error
if stat, err := os.Stat(pp.vehicle.Path()); err == nil {
buf, err = ioutil.ReadFile(pp.vehicle.Path())
modTime := stat.ModTime()
pp.updatedAt = &modTime
} else {
buf, err = pp.vehicle.Read()
}
if err != nil {
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 {
func (pp *proxySetProvider) Type() ProviderType {
return Proxy
}
func (pp *ProxySetProvider) Proxies() []C.Proxy {
func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *ProxySetProvider) pullLoop() {
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) {
func proxiesParse(buf []byte) (interface{}, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
@ -228,38 +139,52 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
return proxies, nil
}
func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) {
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
go pp.healthCheck.check()
}
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
pd.fetcher.Destroy()
}
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
if hc.auto() {
go hc.process()
}
return &ProxySetProvider{
name: name,
vehicle: vehicle,
pd := &proxySetProvider{
proxies: []C.Proxy{},
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 {
*compatibleProvider
}
type compatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
}
func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) {
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": cp.Name(),
"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
}
func (cp *CompatibleProvider) Reload() error {
return nil
}
func (cp *CompatibleProvider) Destroy() error {
cp.healthCheck.close()
return nil
}
func (cp *CompatibleProvider) HealthCheck() {
func (cp *compatibleProvider) HealthCheck() {
cp.healthCheck.check()
}
func (cp *CompatibleProvider) Update() error {
func (cp *compatibleProvider) Update() error {
return nil
}
func (cp *CompatibleProvider) Initial() error {
func (cp *compatibleProvider) Initial() error {
return nil
}
func (cp *CompatibleProvider) VehicleType() VehicleType {
func (cp *compatibleProvider) VehicleType() VehicleType {
return Compatible
}
func (cp *CompatibleProvider) Type() ProviderType {
func (cp *compatibleProvider) Type() ProviderType {
return Proxy
}
func (cp *CompatibleProvider) Proxies() []C.Proxy {
func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close()
}
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least")
@ -314,9 +234,13 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
go hc.process()
}
return &CompatibleProvider{
pd := &compatibleProvider{
name: name,
proxies: proxies,
healthCheck: hc,
}, nil
}
wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
return wrapper, nil
}

View File

@ -4,6 +4,7 @@ import (
"context"
"io/ioutil"
"net/http"
"net/url"
"time"
"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)
defer cancel()
req, err := http.NewRequest(http.MethodGet, h.url, nil)
uri, err := url.Parse(h.url)
if err != nil {
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)
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
// least recently used entries from memory when (if set) the entries are
// older than maxAge (in seconds). Use the New constructor to create one.
@ -52,6 +60,7 @@ type LruCache struct {
cache map[interface{}]*list.Element
lru *list.List // Front is least-recent
updateAgeOnGet bool
staleReturn bool
onEvict EvictCallback
}
@ -72,31 +81,28 @@ func NewLRUCache(options ...Option) *LruCache {
// Get returns the interface{} representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key]
if !ok {
entry := c.get(key)
if entry == nil {
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
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
func (c *LruCache) Exist(key interface{}) bool {
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.
func (c *LruCache) Set(key interface{}, value interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
expires := int64(0)
if c.maxAge > 0 {
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 {
c.lru.MoveToBack(le)
e := le.Value.(*entry)
e.value = value
e.expires = expires
e.expires = expires.Unix()
} 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)
if c.maxSize > 0 {
@ -135,6 +146,30 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
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.
func (c *LruCache) Delete(key string) {
c.mu.Lock()
@ -147,7 +182,7 @@ func (c *LruCache) Delete(key string) {
}
func (c *LruCache) maybeDeleteOldest() {
if c.maxAge > 0 {
if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
c.deleteElement(le)

View File

@ -136,3 +136,31 @@ func TestEvict(t *testing.T) {
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
once sync.Once
errOnce sync.Once
result interface{}
err error
}
func newPicker(ctx context.Context, cancel func()) *Picker {
@ -49,6 +51,11 @@ func (p *Picker) Wait() interface{} {
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.
// 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)) {
@ -64,6 +71,10 @@ func (p *Picker) Go(f func() (interface{}, error)) {
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()
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
import (
"sync"
)
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
bufferSize = 20 * 1024
RelayBufferSize = 20 * 1024
)
// BufPool provide buffer for relay
var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func Get(size int) []byte {
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
}
func (s *Single) Reset() {
s.last = time.Time{}
}
func NewSingle(wait time.Duration) *Single {
return &Single{wait: wait}
}

View File

@ -19,7 +19,7 @@ func TestBasic(t *testing.T) {
}
var wg sync.WaitGroup
const n = 10
const n = 5
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
@ -33,7 +33,7 @@ func TestBasic(t *testing.T) {
wg.Wait()
assert.Equal(t, 1, foo)
assert.Equal(t, 9, shardCount)
assert.Equal(t, 4, shardCount)
}
func TestTimer(t *testing.T) {
@ -51,3 +51,18 @@ func TestTimer(t *testing.T) {
assert.Equal(t, 1, foo)
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()
if v == nil {
errors = append(errors, fmt.Sprintf("filed %s invalid", fieldName))
continue
}
currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil {
errors = append(errors, err.Error())

View File

@ -8,22 +8,26 @@ import (
"github.com/Dreamacro/clash/component/resolver"
)
func Dialer() *net.Dialer {
func Dialer() (*net.Dialer, error) {
dialer := &net.Dialer{}
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{}
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) {
@ -38,7 +42,10 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
return nil, err
}
dialer := Dialer()
dialer, err := Dialer()
if err != nil {
return nil, err
}
var ip net.IP
switch network {
@ -53,7 +60,9 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
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))
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) {
lc := ListenConfig()
lc, err := ListenConfig()
if err != nil {
return nil, err
}
if ListenPacketHook != nil && address == "" {
ip := ListenPacketHook()
if ip != nil {
address = net.JoinHostPort(ip.String(), "0")
ip, err := ListenPacketHook()
if err != nil {
return nil, err
}
address = net.JoinHostPort(ip.String(), "0")
}
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
startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
dialer := Dialer()
result := dialResult{ipv6: ipv6, done: true}
defer func() {
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
if ipv6 {
ip, result.error = resolver.ResolveIPv6(host)
@ -119,7 +137,9 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con
result.resolved = true
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))
}

View File

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

View File

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

View File

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

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 {
once.Do(func() {
var err error

View File

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

View File

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

View File

@ -178,7 +178,7 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr,
}
command = buf[1]
addr, err = readAddr(rw, buf)
addr, err = ReadAddr(rw, buf)
if err != nil {
return
}
@ -260,10 +260,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
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 {
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
type Option struct {
Host string
Port string
Path string
Headers map[string]string
TLSConfig *tls.Config
TLS bool
SkipCertVerify bool
SessionCache tls.ClientSessionCache
Mux bool
}
@ -26,14 +29,16 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) {
config := &vmess.WebsocketConfig{
Host: option.Host,
Port: option.Port,
Path: option.Path,
TLS: option.TLSConfig != nil,
TLS: option.TLS,
Headers: header,
TLSConfig: option.TLSConfig,
SkipCertVerify: option.SkipCertVerify,
SessionCache: option.SessionCache,
}
var err error
conn, err = vmess.NewWebsocketConn(conn, config)
conn, err = vmess.StreamWebsocketConn(conn, config)
if err != nil {
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) {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
length := len(b)
for {
if length == 0 {
@ -73,7 +73,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
n := copy(b, r.buf[r.offset:])
r.offset += n
if r.offset == len(r.buf) {
pool.BufPool.Put(r.buf[:cap(r.buf)])
pool.Put(r.buf)
r.buf = 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")
}
buf := pool.BufPool.Get().([]byte)
buf := pool.Get(size)
_, err = io.ReadFull(r.Reader, buf[:size])
if err != nil {
pool.BufPool.Put(buf[:cap(buf)])
pool.Put(buf)
return 0, err
}
@ -107,7 +107,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
realLen := size - r.Overhead()
n := copy(b, buf[:realLen])
if len(b) >= realLen {
pool.BufPool.Put(buf[:cap(buf)])
pool.Put(buf)
return n, nil
}

View File

@ -34,7 +34,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
n := copy(b, cr.buf[cr.offset:])
cr.offset += n
if cr.offset == len(cr.buf) {
pool.BufPool.Put(cr.buf[:cap(cr.buf)])
pool.Put(cr.buf)
cr.buf = nil
}
return n, nil
@ -59,15 +59,15 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
return size, nil
}
buf := pool.BufPool.Get().([]byte)
_, err = io.ReadFull(cr.Reader, buf[:size])
buf := pool.Get(size)
_, err = io.ReadFull(cr.Reader, buf)
if err != nil {
pool.BufPool.Put(buf[:cap(buf)])
pool.Put(buf)
return 0, err
}
n := copy(b, cr.buf[:])
n := copy(b, buf)
cr.offset = n
cr.buf = buf[:size]
cr.buf = buf
return n, nil
}
@ -76,8 +76,8 @@ type chunkWriter struct {
}
func (cw *chunkWriter) Write(b []byte) (n int, err error) {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
length := len(b)
for {
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"
"math/rand"
"net"
"net/http"
"runtime"
"sync"
@ -69,10 +68,6 @@ type Client struct {
user []*ID
uuid *uuid.UUID
security Security
tls bool
host string
wsConfig *WebsocketConfig
tlsConfig *tls.Config
}
// Config of vmess
@ -80,28 +75,13 @@ type Config struct {
UUID string
AlterID uint16
Security string
TLS bool
HostName string
Port string
NetWork string
WebSocketPath string
WebSocketHeaders map[string]string
SkipCertVerify bool
SessionCache tls.ClientSessionCache
HostName string
}
// New return a Conn with net.Conn and DstAddr
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error
// StreamConn return a Conn with net.Conn and DstAddr
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
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)
}
@ -129,57 +109,9 @@ func NewClient(config Config) (*Client, error) {
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{
user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid,
security: security,
tls: config.TLS,
host: host,
wsConfig: wsConfig,
tlsConfig: tlsConfig,
}, 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 {
Host string
Port string
Path string
Headers http.Header
TLS bool
TLSConfig *tls.Config
SkipCertVerify bool
SessionCache tls.ClientSessionCache
}
// Read implements net.Conn.Read()
@ -111,7 +113,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
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{
NetDial: func(network, addr string) (net.Conn, error) {
return conn, nil
@ -124,17 +126,20 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
scheme := "ws"
if c.TLS {
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 (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
host = c.Host
if host := c.Headers.Get("Host"); host != "" {
dialer.TLSClientConfig.ServerName = host
}
}
uri := url.URL{
Scheme: scheme,
Host: host,
Host: net.JoinHostPort(c.Host, c.Port),
Path: c.Path,
}
@ -151,7 +156,7 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
if resp != nil {
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{

View File

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

View File

@ -6,6 +6,7 @@ import (
"net/http"
"os"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
@ -27,6 +28,28 @@ func downloadMMDB(path string) (err error) {
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
func Init(dir string) error {
// initial homedir
@ -48,11 +71,8 @@ func Init(dir string) error {
}
// initial mmdb
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 err := initMMDB(); err != nil {
return fmt.Errorf("Can't initial MMDB: %w", err)
}
return nil
}

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"math/rand"
"net"
"strings"
@ -41,7 +42,7 @@ type Resolver struct {
fallback []dnsClient
fallbackFilters []fallbackFilter
group singleflight.Group
cache *cache.Cache
lruCache *cache.LruCache
}
// 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]
cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil {
cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if hit {
now := time.Now()
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()))
}
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() {
if msg == nil {
return
}
putMsgToCache(r.cache, q.String(), msg)
putMsgToCache(r.lruCache, q.String(), msg)
if r.mapping {
ips := r.msgToIP(msg)
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)
if isIPReq {
msg, err := r.fallbackExchange(m)
return msg, err
return r.fallbackExchange(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 {
msg = ret.(*D.Msg)
if shared {
msg = msg.Copy()
}
}
return
@ -138,7 +154,7 @@ func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
return r.pool.LookBack(ip)
}
cache := r.cache.Get(ip.String())
cache, _ := r.lruCache.Get(ip.String())
if cache == nil {
return "", false
}
@ -168,13 +184,23 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
for _, client := range clients {
r := client
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()
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)
@ -192,9 +218,9 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
res := <-msgCh
if res.Error == nil {
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if r.shouldFallback(ips[0]) {
go func() { <-fallbackMsg }()
if !r.shouldFallback(ips[0]) {
msg = res.Msg
err = res.Error
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 {
ch := make(chan *result)
ch := make(chan *result, 1)
go func() {
res, err := r.batchExchange(client, msg)
ch <- &result{Msg: res, Error: err}
@ -282,13 +308,13 @@ type Config struct {
func New(config Config) *Resolver {
defaultResolver := &Resolver{
main: transform(config.Default, nil),
cache: cache.New(time.Second * 60),
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
}
r := &Resolver{
ipv6: config.IPv6,
main: transform(config.Main, defaultResolver),
cache: cache.New(time.Second * 60),
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP,
pool: config.Pool,

View File

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

View File

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

22
go.mod
View File

@ -1,22 +1,22 @@
module github.com/Dreamacro/clash
go 1.13
go 1.14
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.5
github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.0.3+incompatible
github.com/go-chi/cors v1.0.0
github.com/go-chi/chi v4.1.1+incompatible
github.com/go-chi/cors v1.1.1
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.1
github.com/miekg/dns v1.1.27
github.com/gofrs/uuid v3.3.0+incompatible
github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.29
github.com/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
gopkg.in/eapache/channels.v1 v1.1.0
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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY=
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
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/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.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.1/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.1/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.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
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.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
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/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
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.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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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-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-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
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-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-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
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-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-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-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-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
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/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=
@ -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/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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/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) {
oldProviders := tunnel.Providers()
// close providers goroutine
for _, provider := range oldProviders {
provider.Destroy()
}
tunnel.UpdateProxies(proxies, providers)
}

View File

@ -3,15 +3,40 @@ package hub
import (
"github.com/Dreamacro/clash/hub/executor"
"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
func Parse() error {
func Parse(options ...Option) error {
cfg, err := executor.Parse()
if err != nil {
return err
}
for _, option := range options {
option(cfg)
}
if 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")
text := strings.SplitN(header, " ", 2)
hasUnvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret {
hasInvalidHeader := text[0] != "Bearer"
hasInvalidSecret := len(text) != 2 || text[1] != serverSecret
if hasInvalidHeader || hasInvalidSecret {
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized)
return

View File

@ -55,6 +55,11 @@ func (l LogLevel) MarshalJSON() ([]byte, error) {
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 {
switch l {
case INFO:

39
main.go
View File

@ -10,22 +10,38 @@ import (
"syscall"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log"
)
var (
flagset map[string]bool
version bool
testConfig bool
homeDir string
configFile string
externalUI string
externalController string
secret string
)
func init() {
flag.StringVar(&homeDir, "d", "", "set configuration directory")
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(&testConfig, "t", false, "test configuration and exit")
flag.Parse()
flagset = map[string]bool{}
flag.Visit(func(f *flag.Flag) {
flagset[f.Name] = true
})
}
func main() {
@ -57,7 +73,28 @@ func main() {
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())
}

View File

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

View File

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

View File

@ -7,33 +7,32 @@ import (
"github.com/Dreamacro/clash/component/socks5"
)
type fakeConn struct {
net.PacketConn
type packet struct {
pc net.PacketConn
rAddr net.Addr
payload []byte
bufRef []byte
}
func (c *fakeConn) Data() []byte {
func (c *packet) Data() []byte {
return c.payload
}
// 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)
if err != nil {
return
}
return c.PacketConn.WriteTo(packet, c.rAddr)
return c.pc.WriteTo(packet, c.rAddr)
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *fakeConn) LocalAddr() net.Addr {
func (c *packet) LocalAddr() net.Addr {
return c.rAddr
}
func (c *fakeConn) Close() error {
err := c.PacketConn.Close()
pool.BufPool.Put(c.bufRef[:cap(c.bufRef)])
return err
func (c *packet) Drop() {
pool.Put(c.bufRef)
return
}

View File

@ -18,8 +18,8 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
req := request.R
host := req.Host
inboundReeder := bufio.NewReader(request)
outboundReeder := bufio.NewReader(outbound)
inboundReader := bufio.NewReader(request)
outboundReader := bufio.NewReader(outbound)
for {
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:
resp, err := http.ReadResponse(outboundReeder, req)
resp, err := http.ReadResponse(outboundReader, req)
if err != nil {
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
buf := pool.BufPool.Get().([]byte)
buf := pool.Get(pool.RelayBufferSize)
_, err = io.CopyBuffer(request, resp.Body, buf)
pool.BufPool.Put(buf[:cap(buf)])
pool.Put(buf)
if err != nil && err != io.EOF {
break
}
req, err = http.ReadRequest(inboundReeder)
req, err = http.ReadRequest(inboundReader)
if err != nil {
break
}
@ -82,15 +82,16 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
}
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
defer packet.Drop()
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
return
}
DefaultManager.Upload() <- int64(len(packet.Data()))
}
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
defer natTable.Delete(key)
defer pc.Close()
@ -101,11 +102,14 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) {
return
}
if fAddr != nil {
from = fAddr
}
n, err = packet.WriteBack(buf[:n], from)
if err != nil {
return
}
DefaultManager.Download() <- int64(n)
}
}
@ -118,16 +122,16 @@ func relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
buf := pool.BufPool.Get().([]byte)
buf := pool.Get(pool.RelayBufferSize)
_, err := io.CopyBuffer(leftConn, rightConn, buf)
pool.BufPool.Put(buf[:cap(buf)])
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.BufPool.Get().([]byte)
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(rightConn, leftConn, buf)
pool.BufPool.Put(buf[:cap(buf)])
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch
}

View File

@ -51,6 +51,11 @@ func (m TunnelMode) MarshalJSON() ([]byte, error) {
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 {
switch m {
case Global:

View File

@ -103,6 +103,14 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
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 {
ut.manager.Leave(ut)
return ut.PacketConn.Close()

View File

@ -147,6 +147,9 @@ func preHandleMetadata(metadata *C.Metadata) error {
metadata.AddrType = C.AtypDomainName
if enhancedMode.FakeIPEnabled() {
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) {
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
@ -182,6 +185,12 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
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 {
log.Debugln("[Metadata PreHandle] error: %s", err)
return
@ -231,7 +240,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
natTable.Set(key, pc)
natTable.Delete(lockKey)
wg.Done()
go handleUDPToLocal(packet.UDPPacket, pc, key)
go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr)
}
wg.Wait()