Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
c0a2473160 | |||
70a19b999d | |||
e54f51af81 | |||
b068466108 | |||
b562f28c1b | |||
230e01f078 | |||
082847b403 | |||
9471d80785 | |||
b263095533 | |||
14d5137703 | |||
d8a771916a | |||
f7f30d3406 | |||
b2c9cbb43e | |||
c733f80793 | |||
88d8f93793 | |||
e57a13ed7a | |||
23525ecc15 | |||
814bd05315 | |||
e81b88fb94 | |||
c4994d6429 | |||
0740d20ba0 | |||
9eaca6e4ab |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@ -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
|
||||
|
89
README.md
89
README.md
@ -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
|
||||
|
||||
@ -94,7 +92,7 @@ allow-lan: false
|
||||
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
||||
# bind-address: "*"
|
||||
|
||||
# Rule / Global/ Direct (default is Rule)
|
||||
# Rule / Global / Direct (default is Rule)
|
||||
mode: Rule
|
||||
|
||||
# set log level to stdout (default is info)
|
||||
@ -150,7 +148,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
|
||||
@ -243,7 +241,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 proxy-group. 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 +305,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 +355,7 @@ Rule:
|
||||
## Documentations
|
||||
https://clash.gitbook.io/
|
||||
|
||||
## Thanks
|
||||
## Credits
|
||||
|
||||
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||
|
||||
|
@ -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,12 @@ 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 NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
|
||||
return &Base{name, addr, tp, udp}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
@ -61,7 +70,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 +203,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
|
||||
|
@ -24,7 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
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) {
|
||||
|
@ -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,
|
||||
|
@ -52,6 +52,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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@ -20,7 +21,6 @@ import (
|
||||
|
||||
type ShadowSocks struct {
|
||||
*Base
|
||||
server string
|
||||
cipher core.Cipher
|
||||
|
||||
// obfs
|
||||
@ -59,28 +59,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 +95,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 +111,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,22 +138,22 @@ 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
|
||||
|
||||
@ -171,10 +177,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
return &ShadowSocks{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Shadowsocks,
|
||||
udp: option.UDP,
|
||||
},
|
||||
server: server,
|
||||
cipher: ciph,
|
||||
|
||||
obfsMode: obfsMode,
|
||||
@ -209,7 +215,17 @@ func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
if e != nil {
|
||||
return 0, nil, e
|
||||
}
|
||||
|
||||
addr := socks5.SplitAddr(b[:n])
|
||||
if addr == nil {
|
||||
return 0, nil, errors.New("parse addr error")
|
||||
}
|
||||
|
||||
udpAddr := addr.UDPAddr()
|
||||
if udpAddr == nil {
|
||||
return 0, nil, errors.New("parse addr error")
|
||||
}
|
||||
|
||||
copy(b, b[len(addr):])
|
||||
return n - len(addr), addr.UDPAddr(), e
|
||||
return n - len(addr), udpAddr, e
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
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
116
adapters/outbound/trojan.go
Normal file
@ -0,0 +1,116 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/trojan"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
*Base
|
||||
instance *trojan.Trojan
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c, err := t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.StreamConn(c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.instance.StreamConn(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(&trojanPacketConn{pc, c}, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": t.Type().String(),
|
||||
})
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
tOption := &trojan.Option{
|
||||
Password: option.Password,
|
||||
ALPN: option.ALPN,
|
||||
ServerName: option.Server,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if option.SNI != "" {
|
||||
tOption.ServerName = option.SNI
|
||||
}
|
||||
|
||||
return &Trojan{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
},
|
||||
instance: trojan.New(tOption),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type trojanPacketConn struct {
|
||||
net.PacketConn
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p)
|
||||
}
|
@ -16,7 +16,6 @@ import (
|
||||
|
||||
type Vmess struct {
|
||||
*Base
|
||||
server string
|
||||
client *vmess.Client
|
||||
}
|
||||
|
||||
@ -35,14 +34,19 @@ type VmessOption struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
}
|
||||
|
||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return v.client.New(c, parseVmessAddr(metadata))
|
||||
}
|
||||
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
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))
|
||||
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,9 +61,9 @@ 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))
|
||||
@ -91,10 +95,10 @@ 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,
|
||||
}, nil
|
||||
}
|
||||
|
@ -77,7 +77,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,
|
||||
}
|
||||
|
@ -120,7 +120,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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
84
adapters/outboundgroup/relay.go
Normal file
84
adapters/outboundgroup/relay.go
Normal file
@ -0,0 +1,84 @@
|
||||
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()
|
||||
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.proxies() {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"type": r.Type().String(),
|
||||
"all": all,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Relay) proxies() []C.Proxy {
|
||||
elm, _, _ := r.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(r.providers), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func NewRelay(name string, providers []provider.ProxyProvider) *Relay {
|
||||
return &Relay{
|
||||
Base: outbound.NewBase(name, "", C.Relay, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ func (s *Selector) proxies() []C.Proxy {
|
||||
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
|
||||
selected := providers[0].Proxies()[0]
|
||||
return &Selector{
|
||||
Base: outbound.NewBase(name, C.Selector, false),
|
||||
Base: outbound.NewBase(name, "", C.Selector, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
selected: selected,
|
||||
|
@ -86,7 +86,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,
|
||||
|
53
adapters/outboundgroup/util.go
Normal file
53
adapters/outboundgroup/util.go
Normal file
@ -0,0 +1,53 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
||||
host, port, err := net.SplitHostPort(rawAddress)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("addrToMetadata failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
if ip.To4() != nil {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
} else {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypIPv6,
|
||||
Host: "",
|
||||
DstIP: ip,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
addr = &C.Metadata{
|
||||
AddrType: C.AtypDomainName,
|
||||
Host: host,
|
||||
DstIP: nil,
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
tcp.SetKeepAlive(true)
|
||||
tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
}
|
@ -114,10 +114,12 @@ func (pp *ProxySetProvider) Destroy() error {
|
||||
func (pp *ProxySetProvider) Initial() error {
|
||||
var buf []byte
|
||||
var err error
|
||||
var isLocal bool
|
||||
if stat, err := os.Stat(pp.vehicle.Path()); err == nil {
|
||||
buf, err = ioutil.ReadFile(pp.vehicle.Path())
|
||||
modTime := stat.ModTime()
|
||||
pp.updatedAt = &modTime
|
||||
isLocal = true
|
||||
} else {
|
||||
buf, err = pp.vehicle.Read()
|
||||
}
|
||||
@ -128,11 +130,20 @@ func (pp *ProxySetProvider) Initial() error {
|
||||
|
||||
proxies, err := pp.parse(buf)
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse local file error, fallback to remote
|
||||
buf, err = pp.vehicle.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxies, err = pp.parse(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
|
||||
|
@ -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())
|
||||
|
@ -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
223
component/trojan/trojan.go
Normal file
@ -0,0 +1,223 @@
|
||||
package trojan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
)
|
||||
|
||||
const (
|
||||
// max packet length
|
||||
maxLength = 8192
|
||||
)
|
||||
|
||||
var (
|
||||
defaultALPN = []string{"h2", "http/1.1"}
|
||||
crlf = []byte{'\r', '\n'}
|
||||
|
||||
bufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||
)
|
||||
|
||||
type Command = byte
|
||||
|
||||
var (
|
||||
CommandTCP byte = 1
|
||||
CommandUDP byte = 3
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Password string
|
||||
ALPN []string
|
||||
ServerName string
|
||||
SkipCertVerify bool
|
||||
ClientSessionCache tls.ClientSessionCache
|
||||
}
|
||||
|
||||
type Trojan struct {
|
||||
option *Option
|
||||
hexPassword []byte
|
||||
}
|
||||
|
||||
func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||
alpn := defaultALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
alpn = t.option.ALPN
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: alpn,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: t.option.SkipCertVerify,
|
||||
ServerName: t.option.ServerName,
|
||||
ClientSessionCache: t.option.ClientSessionCache,
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
defer buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
buf.Write(t.hexPassword)
|
||||
buf.Write(crlf)
|
||||
|
||||
buf.WriteByte(command)
|
||||
buf.Write(socks5Addr)
|
||||
buf.Write(crlf)
|
||||
|
||||
_, err := w.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
|
||||
return &PacketConn{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
defer buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
buf.Write(socks5Addr)
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
|
||||
buf.Write(crlf)
|
||||
buf.Write(payload)
|
||||
|
||||
return w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||
if len(payload) <= maxLength {
|
||||
return writePacket(w, socks5Addr, payload)
|
||||
}
|
||||
|
||||
offset := 0
|
||||
total := len(payload)
|
||||
for {
|
||||
cursor := offset + maxLength
|
||||
if cursor > total {
|
||||
cursor = total
|
||||
}
|
||||
|
||||
n, err := writePacket(w, socks5Addr, payload[offset:cursor])
|
||||
if err != nil {
|
||||
return offset + n, err
|
||||
}
|
||||
|
||||
offset = cursor
|
||||
if offset == total {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
|
||||
addr, err := socks5.ReadAddr(r, payload)
|
||||
if err != nil {
|
||||
return nil, 0, 0, errors.New("read addr error")
|
||||
}
|
||||
uAddr := addr.UDPAddr()
|
||||
|
||||
if _, err = io.ReadFull(r, payload[:2]); err != nil {
|
||||
return nil, 0, 0, errors.New("read length error")
|
||||
}
|
||||
|
||||
total := int(binary.BigEndian.Uint16(payload[:2]))
|
||||
if total > maxLength {
|
||||
return nil, 0, 0, errors.New("packet invalid")
|
||||
}
|
||||
|
||||
// read crlf
|
||||
if _, err = io.ReadFull(r, payload[:2]); err != nil {
|
||||
return nil, 0, 0, errors.New("read crlf error")
|
||||
}
|
||||
|
||||
length := len(payload)
|
||||
if total < length {
|
||||
length = total
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(r, payload[:length]); err != nil {
|
||||
return nil, 0, 0, errors.New("read packet error")
|
||||
}
|
||||
|
||||
return uAddr, length, total - length, nil
|
||||
}
|
||||
|
||||
func New(option *Option) *Trojan {
|
||||
return &Trojan{option, hexSha224([]byte(option.Password))}
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.Conn
|
||||
remain int
|
||||
rAddr net.Addr
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (pc *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
return WritePacket(pc, socks5.ParseAddr(addr.String()), b)
|
||||
}
|
||||
|
||||
func (pc *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
pc.mux.Lock()
|
||||
defer pc.mux.Unlock()
|
||||
if pc.remain != 0 {
|
||||
length := len(b)
|
||||
if pc.remain < length {
|
||||
length = pc.remain
|
||||
}
|
||||
|
||||
n, err := pc.Conn.Read(b[:length])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
pc.remain -= n
|
||||
addr := pc.rAddr
|
||||
if pc.remain == 0 {
|
||||
pc.rAddr = nil
|
||||
}
|
||||
|
||||
return n, addr, nil
|
||||
}
|
||||
|
||||
addr, n, remain, err := ReadPacket(pc.Conn, b)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if remain != 0 {
|
||||
pc.remain = remain
|
||||
pc.rAddr = addr
|
||||
}
|
||||
|
||||
return n, addr, nil
|
||||
}
|
||||
|
||||
func hexSha224(data []byte) []byte {
|
||||
buf := make([]byte, 56)
|
||||
hash := sha256.New224()
|
||||
hash.Write(data)
|
||||
hex.Encode(buf, hash.Sum(nil))
|
||||
return buf
|
||||
}
|
@ -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,6 +256,18 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
groupsConfig := cfg.ProxyGroup
|
||||
providersConfig := cfg.ProxyProvider
|
||||
|
||||
if len(proxiesConfig) == 0 {
|
||||
proxiesConfig = cfg.ProxyOld
|
||||
}
|
||||
|
||||
if len(groupsConfig) == 0 {
|
||||
groupsConfig = cfg.ProxyGroupOld
|
||||
}
|
||||
|
||||
if len(providersConfig) == 0 {
|
||||
providersConfig = cfg.ProxyProviderOld
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Destroy already created provider when err != nil
|
||||
if err != nil {
|
||||
@ -351,6 +374,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, ","))
|
||||
|
@ -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,12 @@ 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
|
||||
}
|
||||
|
||||
type DelayHistory struct {
|
||||
@ -86,12 +92,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 +103,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"
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -118,8 +118,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
ret, err, _ := 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)
|
||||
@ -168,7 +167,13 @@ 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
|
||||
})
|
||||
}
|
||||
|
||||
@ -192,9 +197,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 +257,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}
|
||||
|
@ -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
|
||||
|
8
go.mod
8
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/Dreamacro/clash
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
||||
@ -13,9 +13,9 @@ require (
|
||||
github.com/miekg/dns v1.1.27
|
||||
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
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
gopkg.in/eapache/channels.v1 v1.1.0
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
|
10
go.sum
10
go.sum
@ -34,20 +34,22 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
||||
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-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/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/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
|
@ -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:
|
||||
|
14
main.go
14
main.go
@ -10,13 +10,16 @@ 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 (
|
||||
version bool
|
||||
testConfig bool
|
||||
homeDir string
|
||||
configFile string
|
||||
)
|
||||
@ -25,6 +28,7 @@ func init() {
|
||||
flag.StringVar(&homeDir, "d", "", "set configuration directory")
|
||||
flag.StringVar(&configFile, "f", "", "specify configuration file")
|
||||
flag.BoolVar(&version, "v", false, "show current version of clash")
|
||||
flag.BoolVar(&testConfig, "t", false, "test configuration and exit")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
@ -57,6 +61,16 @@ func main() {
|
||||
log.Fatalln("Initial configuration directory error: %s", err.Error())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err := hub.Parse(); err != nil {
|
||||
log.Fatalln("Parse config error: %s", err.Error())
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
75
proxy/redir/udp.go
Normal file
75
proxy/redir/udp.go
Normal file
@ -0,0 +1,75 @@
|
||||
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.BufPool.Get().([]byte)
|
||||
n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob)
|
||||
if err != nil {
|
||||
pool.BufPool.Put(buf[:cap(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)
|
||||
packet := &fakeConn{
|
||||
PacketConn: pc,
|
||||
lAddr: lAddr,
|
||||
buf: buf,
|
||||
}
|
||||
tunnel.AddPacket(adapters.NewPacket(target, packet, C.REDIR))
|
||||
}
|
69
proxy/redir/udp_linux.go
Normal file
69
proxy/redir/udp_linux.go
Normal file
@ -0,0 +1,69 @@
|
||||
// +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_IP, syscall.IP_TRANSPARENT, 1)
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
||||
ip := net.IP(msg.Data[4:8])
|
||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
||||
ip := net.IP(msg.Data[8:24])
|
||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("cannot find origDst")
|
||||
}
|
16
proxy/redir/udp_other.go
Normal file
16
proxy/redir/udp_other.go
Normal file
@ -0,0 +1,16 @@
|
||||
// +build !linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func setsockopt(c *net.UDPConn, addr string) error {
|
||||
return errors.New("UDP redir not supported on current platform")
|
||||
}
|
||||
|
||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||
return nil, errors.New("UDP redir not supported on current platform")
|
||||
}
|
40
proxy/redir/utils.go
Normal file
40
proxy/redir/utils.go
Normal file
@ -0,0 +1,40 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
type fakeConn struct {
|
||||
net.PacketConn
|
||||
lAddr *net.UDPAddr
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (c *fakeConn) Data() []byte {
|
||||
return c.buf
|
||||
}
|
||||
|
||||
// WriteBack opens a new socket binding `addr` to wirte UDP packet back
|
||||
func (c *fakeConn) 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 *fakeConn) LocalAddr() net.Addr {
|
||||
return c.lAddr
|
||||
}
|
||||
|
||||
func (c *fakeConn) Close() error {
|
||||
err := c.PacketConn.Close()
|
||||
pool.BufPool.Put(c.buf[:cap(c.buf)])
|
||||
return err
|
||||
}
|
96
proxy/redir/utils_linux.go
Normal file
96
proxy/redir/utils_linux.go
Normal file
@ -0,0 +1,96 @@
|
||||
// +build linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// dialUDP acts like net.DialUDP for transparent proxy.
|
||||
// It binds to a non-local address(`lAddr`).
|
||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||
rSockAddr, err := udpAddrToSockAddr(rAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lSockAddr, err := udpAddrToSockAddr(lAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.Bind(fd, lSockAddr); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = syscall.Connect(fd, rSockAddr); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String()))
|
||||
defer fdFile.Close()
|
||||
|
||||
c, err := net.FileConn(fdFile)
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.(*net.UDPConn), nil
|
||||
}
|
||||
|
||||
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
|
||||
switch {
|
||||
case addr.IP.To4() != nil:
|
||||
ip := [4]byte{}
|
||||
copy(ip[:], addr.IP.To4())
|
||||
|
||||
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
|
||||
|
||||
default:
|
||||
ip := [16]byte{}
|
||||
copy(ip[:], addr.IP.To16())
|
||||
|
||||
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
|
||||
if err != nil {
|
||||
zoneID = 0
|
||||
}
|
||||
|
||||
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
|
||||
switch net[len(net)-1] {
|
||||
case '4':
|
||||
return syscall.AF_INET
|
||||
case '6':
|
||||
return syscall.AF_INET6
|
||||
}
|
||||
|
||||
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
|
||||
return syscall.AF_INET
|
||||
}
|
||||
return syscall.AF_INET6
|
||||
}
|
12
proxy/redir/utils_other.go
Normal file
12
proxy/redir/utils_other.go
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !linux
|
||||
|
||||
package redir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||
return nil, errors.New("UDP redir not supported on current platform")
|
||||
}
|
@ -88,7 +88,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
|
||||
DefaultManager.Upload() <- int64(len(packet.Data()))
|
||||
}
|
||||
|
||||
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) {
|
||||
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
|
||||
buf := pool.BufPool.Get().([]byte)
|
||||
defer pool.BufPool.Put(buf[:cap(buf)])
|
||||
defer natTable.Delete(key)
|
||||
@ -101,6 +101,10 @@ 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
|
||||
|
@ -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:
|
||||
|
@ -182,6 +182,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 +237,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()
|
||||
|
Reference in New Issue
Block a user