Compare commits

...

32 Commits

Author SHA1 Message Date
8da19e81a4 Update: README.md 2019-02-11 15:55:17 +08:00
1339487ce4 Fix: tun2socks not lookup IP 2019-02-11 15:44:42 +08:00
2383cca2ce Feature: add v2ray-plugin 2019-02-11 15:25:39 +08:00
53b5ef199f Fix: parse proxies shadow variable 2019-02-04 09:39:17 +08:00
754df5ba9b Chore: update dependencies 2019-02-02 21:53:30 +08:00
1016355ef6 Chore: log dns server address when success 2019-02-02 21:37:36 +08:00
b594cbc68d Fix: parse ip string when use socks proxy (#100) 2019-02-02 21:11:27 +08:00
42d33fe629 Feature: SOURCE-IP-CIDR rule type (#96) 2019-02-02 21:03:13 +08:00
bfe51e46b0 Improve: lazy resolve ip 2019-02-02 20:47:38 +08:00
e30a628702 Fix: shadow variable 2019-01-30 20:22:52 +08:00
5581698908 Chore: improve programming style (#109) 2019-01-29 23:46:18 +08:00
36b5d1f18f Fix: DNS server returns the correct TTL 2019-01-25 15:38:14 +08:00
bd6c6a9ad1 Chore: print origin rule when format error (#92) 2019-01-14 10:35:11 +08:00
83fac44010 Fix: nghttpx return 400 error (#84) 2019-01-07 10:47:25 +08:00
15a77fa71b Fix: print log when start dns server failed (#87) 2019-01-06 14:31:42 +08:00
7768c5b933 Chore: add more platform release (#83) 2019-01-03 10:49:09 +08:00
4e91118a05 Feature: add freebsd release (#80)
add freebsd support
2018-12-31 20:57:21 +08:00
7b5e1f759c Fix: authentication with stream api 2018-12-29 14:11:54 +08:00
3f6c707aa9 Fix: patch config field 2018-12-23 20:25:49 +08:00
532ec88964 Chore: make a consistent code style 2018-12-23 00:42:08 +08:00
cb118d4371 Chore: improve outbound architecture 2018-12-22 23:56:42 +08:00
a7cfc81885 Fix: ignore some general configuration 2018-12-21 22:51:37 +08:00
551ab68c1e Fix: allow access to external-ui without authentication (#75) 2018-12-21 17:48:29 +08:00
ef6260282f Fix: parse external-ui 2018-12-21 10:55:21 +08:00
49635eab6c Chore: update external-ui explanation 2018-12-20 22:34:38 +08:00
a46041b81c Fix: force param make no sense 2018-12-20 22:23:31 +08:00
a6bbc67afb Feature: add custom ui support in API 2018-12-20 01:29:13 +08:00
afc4644dd1 Feature: FreeBSD compatibility patch (#63) 2018-12-18 10:37:00 +08:00
1607d3253f Feature: add websocket headers support in vmess 2018-12-11 00:25:05 +08:00
34c8655974 Fix: don't keepalive when connection is close (#65)
fixed #60
2018-12-10 11:48:57 +08:00
5e4b35e03a Chore: standardize API returns 2018-12-10 11:33:37 +08:00
fa9077969c Fix: dns crash & remove unused debug log 2018-12-10 11:00:52 +08:00
41 changed files with 965 additions and 449 deletions

1
.gitignore vendored
View File

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

View File

@ -2,22 +2,88 @@ NAME=clash
BINDIR=bin
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s'
all: linux macos win64
PLATFORM_LIST = \
darwin-amd64 \
linux-386 \
linux-amd64 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle \
linux-mips64 \
linux-mips64le \
freebsd-386 \
freebsd-amd64
linux:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64
macos:
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
win64:
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv6:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-hardfloat:
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle:
GOARCH=mipsle GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64:
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
releases: linux macos win64
chmod +x $(BINDIR)/$(NAME)-*
gzip $(BINDIR)/$(NAME)-linux
gzip $(BINDIR)/$(NAME)-macos
zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
$(gz_releases): %.gz : %
chmod +x $(BINDIR)/$(NAME)-$(basename $@)
gzip -f $(BINDIR)/$(NAME)-$(basename $@)
$(zip_releases): %.zip : %
zip -m -j $(BINDIR)/$(NAME)-$(basename $@).zip $(BINDIR)/$(NAME)-$(basename $@).exe
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases)
clean:
rm $(BINDIR)/*

View File

@ -1,11 +1,9 @@
<h1 align="center">
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200">
<br>
Clash
<br>
<br>Clash<br>
</h1>
<h4 align="center">A rule based tunnel in Go.</h4>
<h4 align="center">A rule-based tunnel in Go.</h4>
<p align="center">
<a href="https://travis-ci.org/Dreamacro/clash">
@ -13,7 +11,7 @@
alt="Travis-CI">
</a>
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
</a>
<a href="https://github.com/Dreamacro/clash/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
@ -58,8 +56,6 @@ If you have Docker installed, you can run clash directly using `docker-compose`.
## Config
**NOTE: after v0.8.0, clash using yaml as configuration file**
The default configuration directory is `$HOME/.config/clash`
The name of the configuration file is `config.yml`
@ -96,10 +92,14 @@ log-level: info
# A RESTful API for clash
external-controller: 127.0.0.1:9090
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
# input is a relative path to the configuration directory or an absolute path
# external-ui: folder
# Secret for RESTful API (Optional)
# secret: ""
dns:
# dns:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# listen: 0.0.0.0:53
@ -117,7 +117,32 @@ Proxy:
# support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20
# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305
- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" }
- { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com }
# old obfs configuration remove after prerelease
- name: "ss2"
type: ss
server: server
port: 443
cipher: AEAD_CHACHA20_POLY1305
password: "password"
plugin: obfs
plugin-opts:
mode: tls # or http
# host: bing.com
- name: "ss3"
type: ss
server: server
port: 443
cipher: AEAD_CHACHA20_POLY1305
password: "password"
plugin: v2ray-plugin
plugin-opts:
mode: websocket # no QUIC now
# tls: true # wss
# skip-cert-verify: true
# host: bing.com
# path: "/"
# vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
@ -126,8 +151,8 @@ Proxy:
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
# with tls and skip-cert-verify
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
# with ws
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path }
# with ws-path and ws-headers
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } }
# with ws + tls
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
@ -151,10 +176,10 @@ Proxy:
Proxy Group:
# url-test select which proxy will be used by benchmarking speed to a URL.
- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 }
- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 }
- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# select is used for selecting proxy or proxy group
# you can use RESTful API to switch proxy, is recommended for use in GUI.
@ -166,6 +191,7 @@ Rule:
- DOMAIN,google.com,Proxy
- DOMAIN-SUFFIX,ad.com,REJECT
- IP-CIDR,127.0.0.0/8,DIRECT
- SOURCE-IP-CIDR,192.168.1.201/32,DIRECT
- GEOIP,CN,DIRECT
# FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now

View File

@ -32,8 +32,10 @@ func (h *HTTPAdapter) Conn() net.Conn {
// NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
metadata := parseHTTPAddr(request)
metadata.SourceIP = parseSourceIP(conn)
return &HTTPAdapter{
metadata: parseHTTPAddr(request),
metadata: metadata,
R: request,
conn: conn,
}

View File

@ -7,8 +7,10 @@ import (
// NewHTTPS is HTTPAdapter generator
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
metadata := parseHTTPAddr(request)
metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{
metadata: parseHTTPAddr(request),
metadata: metadata,
conn: conn,
}
}

View File

@ -32,6 +32,7 @@ func (s *SocketAdapter) Conn() net.Conn {
func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter {
metadata := parseSocksAddr(target)
metadata.Source = source
metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{
conn: conn,

View File

@ -61,3 +61,10 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata
}
func parseSourceIP(conn net.Conn) *net.IP {
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
return &addr.IP
}
return nil
}

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

@ -0,0 +1,26 @@
package adapters
import (
"encoding/json"
C "github.com/Dreamacro/clash/constant"
)
type Base struct {
name string
tp C.AdapterType
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}

View File

@ -1,38 +1,16 @@
package adapters
import (
"encoding/json"
"net"
C "github.com/Dreamacro/clash/constant"
)
// DirectAdapter is a directly connected adapter
type DirectAdapter struct {
conn net.Conn
type Direct struct {
*Base
}
// Close is used to close connection
func (d *DirectAdapter) Close() {
d.conn.Close()
}
// Conn is used to http request
func (d *DirectAdapter) Conn() net.Conn {
return d.conn
}
type Direct struct{}
func (d *Direct) Name() string {
return "DIRECT"
}
func (d *Direct) Type() C.AdapterType {
return C.Direct
}
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (d *Direct) Generator(metadata *C.Metadata) (net.Conn, error) {
address := net.JoinHostPort(metadata.Host, metadata.Port)
if metadata.IP != nil {
address = net.JoinHostPort(metadata.IP.String(), metadata.Port)
@ -40,18 +18,17 @@ func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er
c, err := net.DialTimeout("tcp", address, tcpTimeout)
if err != nil {
return
return nil, err
}
tcpKeepAlive(c)
return &DirectAdapter{conn: c}, nil
}
func (d *Direct) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": d.Type().String(),
})
return c, nil
}
func NewDirect() *Direct {
return &Direct{}
return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
},
}
}

View File

@ -3,6 +3,7 @@ package adapters
import (
"encoding/json"
"errors"
"net"
"sync"
"time"
@ -15,7 +16,7 @@ type proxy struct {
}
type Fallback struct {
name string
*Base
proxies []*proxy
rawURL string
interval time.Duration
@ -29,14 +30,6 @@ type FallbackOption struct {
Interval int `proxy:"interval"`
}
func (f *Fallback) Name() string {
return f.name
}
func (f *Fallback) Type() C.AdapterType {
return C.Fallback
}
func (f *Fallback) Now() string {
_, proxy := f.findNextValidProxy(0)
if proxy != nil {
@ -45,7 +38,7 @@ func (f *Fallback) Now() string {
return f.proxies[0].RawProxy.Name()
}
func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) {
idx := 0
var proxy *proxy
for {
@ -53,13 +46,13 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err
if proxy == nil {
break
}
adapter, err = proxy.RawProxy.Generator(metadata)
adapter, err := proxy.RawProxy.Generator(metadata)
if err != nil {
proxy.Valid = false
idx++
continue
}
return
return adapter, err
}
return f.proxies[0].RawProxy.Generator(metadata)
}
@ -138,7 +131,10 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
}
Fallback := &Fallback{
name: option.Name,
Base: &Base{
name: option.Name,
tp: C.Fallback,
},
proxies: warpperProxies,
rawURL: option.URL,
interval: interval,

View File

@ -5,7 +5,6 @@ import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
@ -16,23 +15,9 @@ import (
C "github.com/Dreamacro/clash/constant"
)
// HTTPAdapter is a proxy adapter
type HTTPAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ha *HTTPAdapter) Close() {
ha.conn.Close()
}
func (ha *HTTPAdapter) Conn() net.Conn {
return ha.conn
}
type Http struct {
*Base
addr string
name string
user string
pass string
tls bool
@ -50,15 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (h *Http) Name() string {
return h.name
}
func (h *Http) Type() C.AdapterType {
return C.Http
}
func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (h *Http) Generator(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", h.addr, tcpTimeout)
if err == nil && h.tls {
cc := tls.Client(c, h.tlsConfig)
@ -74,16 +51,16 @@ func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err erro
return nil, err
}
return &HTTPAdapter{conn: c}, nil
return c, nil
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
var buf bytes.Buffer
var err error
buf.WriteString("CONNECT ")
buf.WriteString(net.JoinHostPort(metadata.Host, metadata.Port))
buf.WriteString(" HTTP/1.1\r\n")
addr := net.JoinHostPort(metadata.Host, metadata.Port)
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
buf.WriteString("Host: " + metadata.Host + "\r\n")
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
if h.user != "" && h.pass != "" {
@ -118,12 +95,6 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
}
func (h *Http) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": h.Type().String(),
})
}
func NewHttp(option HttpOption) *Http {
var tlsConfig *tls.Config
if option.TLS {
@ -137,8 +108,11 @@ func NewHttp(option HttpOption) *Http {
}
return &Http{
Base: &Base{
name: option.Name,
tp: C.Http,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
user: option.UserName,
pass: option.Password,
tls: option.TLS,

View File

@ -1,7 +1,6 @@
package adapters
import (
"encoding/json"
"io"
"net"
"time"
@ -9,42 +8,21 @@ import (
C "github.com/Dreamacro/clash/constant"
)
// RejectAdapter is a reject connected adapter
type RejectAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (r *RejectAdapter) Close() {}
// Conn is used to http request
func (r *RejectAdapter) Conn() net.Conn {
return r.conn
}
type Reject struct {
*Base
}
func (r *Reject) Name() string {
return "REJECT"
}
func (r *Reject) Type() C.AdapterType {
return C.Reject
}
func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
return &RejectAdapter{conn: &NopConn{}}, nil
}
func (r *Reject) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": r.Type().String(),
})
func (r *Reject) Generator(metadata *C.Metadata) (net.Conn, error) {
return &NopConn{}, nil
}
func NewReject() *Reject {
return &Reject{}
return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
},
}
}
type NopConn struct{}

View File

@ -3,13 +3,14 @@ package adapters
import (
"encoding/json"
"errors"
"net"
"sort"
C "github.com/Dreamacro/clash/constant"
)
type Selector struct {
name string
*Base
selected C.Proxy
proxies map[string]C.Proxy
}
@ -19,15 +20,7 @@ type SelectorOption struct {
Proxies []string `proxy:"proxies"`
}
func (s *Selector) Name() string {
return s.name
}
func (s *Selector) Type() C.AdapterType {
return C.Selector
}
func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (s *Selector) Generator(metadata *C.Metadata) (net.Conn, error) {
return s.selected.Generator(metadata)
}
@ -68,7 +61,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
}
s := &Selector{
name: name,
Base: &Base{
name: name,
tp: C.Selector,
},
proxies: mapping,
selected: proxies[0],
}

View File

@ -2,74 +2,81 @@ package adapters
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
// ShadowsocksAdapter is a shadowsocks adapter
type ShadowsocksAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *ShadowsocksAdapter) Close() {
ss.conn.Close()
}
func (ss *ShadowsocksAdapter) Conn() net.Conn {
return ss.conn
}
type ShadowSocks struct {
server string
name string
obfs string
obfsHost string
cipher core.Cipher
*Base
server string
cipher core.Cipher
// obfs
obfsMode string
obfsOption *simpleObfsOption
wsOption *v2rayObfs.WebsocketOption
}
type ShadowSocksOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"`
}
func (ss *ShadowSocks) Name() string {
return ss.name
type simpleObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
}
func (ss *ShadowSocks) Type() C.AdapterType {
return C.Shadowsocks
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
}
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
if err != nil {
return nil, fmt.Errorf("%s connect error", ss.server)
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
}
tcpKeepAlive(c)
switch ss.obfs {
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost)
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(ss.server)
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewWebsocketObfs(c, ss.wsOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
}
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata))
return &ShadowsocksAdapter{conn: c}, err
return c, err
}
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
@ -87,18 +94,62 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
}
obfs := option.Obfs
obfsHost := "bing.com"
if option.ObfsHost != "" {
obfsHost = option.ObfsHost
var wsOption *v2rayObfs.WebsocketOption
var obfsOption *simpleObfsOption
obfsMode := ""
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
}
obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
wsOption = &v2rayObfs.WebsocketOption{
Host: opts.Host,
Path: opts.Path,
TLSConfig: tlsConfig,
}
}
return &ShadowSocks{
server: server,
name: option.Name,
cipher: ciph,
obfs: obfs,
obfsHost: obfsHost,
Base: &Base{
name: option.Name,
tp: C.Shadowsocks,
},
server: server,
cipher: ciph,
obfsMode: obfsMode,
wsOption: wsOption,
obfsOption: obfsOption,
}, nil
}

View File

@ -3,7 +3,6 @@ package adapters
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
@ -15,23 +14,9 @@ import (
"github.com/Dreamacro/go-shadowsocks2/socks"
)
// Socks5Adapter is a shadowsocks adapter
type Socks5Adapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *Socks5Adapter) Close() {
ss.conn.Close()
}
func (ss *Socks5Adapter) Conn() net.Conn {
return ss.conn
}
type Socks5 struct {
*Base
addr string
name string
user string
pass string
tls bool
@ -49,15 +34,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (ss *Socks5) Name() string {
return ss.name
}
func (ss *Socks5) Type() C.AdapterType {
return C.Socks5
}
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (ss *Socks5) Generator(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls {
@ -73,13 +50,7 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
if err := ss.shakeHand(metadata, c); err != nil {
return nil, err
}
return &Socks5Adapter{conn: c}, nil
}
func (ss *Socks5) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
return c, nil
}
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
@ -154,8 +125,11 @@ func NewSocks5(option Socks5Option) *Socks5 {
}
return &Socks5{
Base: &Base{
name: option.Name,
tp: C.Socks5,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
user: option.UserName,
pass: option.Password,
tls: option.TLS,

View File

@ -3,6 +3,7 @@ package adapters
import (
"encoding/json"
"errors"
"net"
"sort"
"sync"
"sync/atomic"
@ -12,7 +13,7 @@ import (
)
type URLTest struct {
name string
*Base
proxies []C.Proxy
rawURL string
fast C.Proxy
@ -28,19 +29,11 @@ type URLTestOption struct {
Interval int `proxy:"interval"`
}
func (u *URLTest) Name() string {
return u.name
}
func (u *URLTest) Type() C.AdapterType {
return C.URLTest
}
func (u *URLTest) Now() string {
return u.fast.Name()
}
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (u *URLTest) Generator(metadata *C.Metadata) (net.Conn, error) {
a, err := u.fast.Generator(metadata)
if err != nil {
go u.speedTest()
@ -128,7 +121,10 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
interval := time.Duration(option.Interval) * time.Second
urlTest := &URLTest{
name: option.Name,
Base: &Base{
name: option.Name,
tp: C.URLTest,
},
proxies: proxies[:],
rawURL: option.URL,
fast: proxies[0],

View File

@ -36,7 +36,7 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
defer instance.Close()
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance.Conn(), nil
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,

View File

@ -1,7 +1,6 @@
package adapters
import (
"encoding/json"
"fmt"
"net"
"strconv"
@ -11,83 +10,60 @@ import (
C "github.com/Dreamacro/clash/constant"
)
// VmessAdapter is a vmess adapter
type VmessAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (v *VmessAdapter) Close() {
v.conn.Close()
}
func (v *VmessAdapter) Conn() net.Conn {
return v.conn
}
type Vmess struct {
name string
*Base
server string
client *vmess.Client
}
type VmessOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"`
Network string `proxy:"network,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"`
Network string `proxy:"network,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) Name() string {
return v.name
}
func (v *Vmess) Type() C.AdapterType {
return C.Vmess
}
func (v *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
func (v *Vmess) Generator(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
return &VmessAdapter{conn: c}, err
}
func (v *Vmess) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"type": v.Type().String(),
})
return c, err
}
func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{
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,
SkipCertVerify: option.SkipCertVerify,
SessionCacahe: getClientSessionCache(),
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,
SessionCacahe: getClientSessionCache(),
})
if err != nil {
return nil, err
}
return &Vmess{
name: option.Name,
Base: &Base{
name: option.Name,
tp: C.Vmess,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client,
}, nil

15
common/cache/cache.go vendored
View File

@ -44,6 +44,21 @@ func (c *cache) Get(key interface{}) interface{} {
return elm.Payload
}
// GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) {
item, exist := c.mapping.Load(key)
if !exist {
return
}
elm := item.(*element)
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return
}
return elm.Payload, elm.Expired
}
func (c *cache) cleanup() {
c.mapping.Range(func(k, v interface{}) bool {
key := k.(string)

View File

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

View File

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

View File

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

View File

@ -69,22 +69,23 @@ type Client struct {
security Security
tls bool
host string
wsConfig *websocketConfig
wsConfig *WebsocketConfig
tlsConfig *tls.Config
}
// Config of vmess
type Config struct {
UUID string
AlterID uint16
Security string
TLS bool
HostName string
Port string
NetWork string
WebSocketPath string
SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
UUID string
AlterID uint16
Security string
TLS bool
HostName string
Port string
NetWork string
WebSocketPath string
WebSocketHeaders map[string]string
SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
}
// New return a Conn with net.Conn and DstAddr
@ -92,7 +93,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error
r := rand.Intn(len(c.user))
if c.wsConfig != nil {
conn, err = newWebsocketConn(conn, c.wsConfig)
conn, err = NewWebsocketConn(conn, c.wsConfig)
if err != nil {
return nil, err
}
@ -144,13 +145,14 @@ func NewClient(config Config) (*Client, error) {
}
}
var wsConfig *websocketConfig
var wsConfig *WebsocketConfig
if config.NetWork == "ws" {
wsConfig = &websocketConfig{
host: host,
path: config.WebSocketPath,
tls: config.TLS,
tlsConfig: tlsConfig,
wsConfig = &WebsocketConfig{
Host: host,
Path: config.WebSocketPath,
Headers: config.WebSocketHeaders,
TLS: config.TLS,
TLSConfig: tlsConfig,
}
}

View File

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

View File

@ -6,6 +6,7 @@ import (
"net"
"net/url"
"os"
"path/filepath"
"strings"
adapters "github.com/Dreamacro/clash/adapters/outbound"
@ -27,8 +28,9 @@ type General struct {
AllowLan bool `json:"allow-lan"`
Mode T.Mode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"external-controller,omitempty"`
Secret string `json:"secret,omitempty"`
ExternalController string `json:"-"`
ExternalUI string `json:"-"`
Secret string `json:"-"`
}
// DNS config
@ -66,9 +68,10 @@ type rawConfig struct {
Mode T.Mode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"`
ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"`
DNS *rawDNS `yaml:"dns"`
DNS rawDNS `yaml:"dns"`
Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"`
@ -95,7 +98,7 @@ func readConfig(path string) (*rawConfig, error) {
Rule: []string{},
Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{},
DNS: &rawDNS{
DNS: rawDNS{
Enable: false,
},
}
@ -145,10 +148,21 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
redirPort := cfg.RedirPort
allowLan := cfg.AllowLan
externalController := cfg.ExternalController
externalUI := cfg.ExternalUI
secret := cfg.Secret
mode := cfg.Mode
logLevel := cfg.LogLevel
if externalUI != "" {
if !filepath.IsAbs(externalUI) {
externalUI = filepath.Join(C.Path.HomeDir(), externalUI)
}
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
}
}
general := &General{
Port: port,
SocksPort: socksPort,
@ -157,6 +171,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
Mode: mode,
LogLevel: logLevel,
ExternalController: externalController,
ExternalUI: externalUI,
Secret: secret,
}
return general, nil
@ -236,6 +251,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
}
var group C.Proxy
var ps []C.Proxy
var err error
switch groupType {
case "url-test":
@ -245,7 +261,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
break
}
ps, err := getProxies(proxies, urlTestOption.Proxies)
ps, err = getProxies(proxies, urlTestOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
@ -257,7 +273,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
break
}
ps, err := getProxies(proxies, selectorOption.Proxies)
ps, err = getProxies(proxies, selectorOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
@ -269,7 +285,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
break
}
ps, err := getProxies(proxies, fallbackOption.Proxies)
ps, err = getProxies(proxies, fallbackOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
@ -309,7 +325,7 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
payload = rule[1]
target = rule[2]
default:
return nil, fmt.Errorf("Rules[%d] error: format invalid", idx)
return nil, fmt.Errorf("Rules[%d] [- %s] error: format invalid", idx, line)
}
rule = trimArr(rule)
@ -323,7 +339,9 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
case "GEOIP":
rules = append(rules, R.NewGEOIP(payload, target))
case "IP-CIDR", "IP-CIDR6":
rules = append(rules, R.NewIPCIDR(payload, target))
rules = append(rules, R.NewIPCIDR(payload, target, false))
case "SOURCE-IP-CIDR":
rules = append(rules, R.NewIPCIDR(payload, target, true))
case "MATCH":
fallthrough
case "FINAL":
@ -353,7 +371,6 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers := []dns.NameServer{}
log.Debugln("%#v", servers)
for idx, server := range servers {
// parse without scheme .e.g 8.8.8.8:53
@ -387,7 +404,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nameservers, nil
}
func parseDNS(cfg *rawDNS) (*DNS, error) {
func parseDNS(cfg rawDNS) (*DNS, error) {
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
}

View File

@ -17,11 +17,6 @@ const (
Vmess
)
type ProxyAdapter interface {
Conn() net.Conn
Close()
}
type ServerAdapter interface {
Metadata() *Metadata
Close()
@ -30,7 +25,7 @@ type ServerAdapter interface {
type Proxy interface {
Name() string
Type() AdapterType
Generator(metadata *Metadata) (ProxyAdapter, error)
Generator(metadata *Metadata) (net.Conn, error)
MarshalJSON() ([]byte, error)
}

View File

@ -33,15 +33,20 @@ type SourceType int
type Metadata struct {
NetWork NetWork
Source SourceType
SourceIP *net.IP
AddrType int
Host string
IP *net.IP
Port string
}
func (addr *Metadata) String() string {
if addr.Host == "" {
return addr.IP.String()
func (m *Metadata) String() string {
if m.Host == "" {
return m.IP.String()
}
return addr.Host
return m.Host
}
func (m *Metadata) Valid() bool {
return m.Host != "" || m.IP != nil
}

View File

@ -7,6 +7,7 @@ const (
DomainKeyword
GEOIP
IPCIDR
SourceIPCIDR
FINAL
)
@ -24,6 +25,8 @@ func (rt RuleType) String() string {
return "GEOIP"
case IPCIDR:
return "IPCIDR"
case SourceIPCIDR:
return "SourceIPCIDR"
case FINAL:
return "FINAL"
default:

View File

@ -52,9 +52,16 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
}
q := m.Question[0]
cache := r.cache.Get(q.String())
cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil {
return cache.(*D.Msg).Copy(), nil
msg = cache.(*D.Msg).Copy()
if len(msg.Answer) > 0 {
ttl := uint32(expireTime.Sub(time.Now()).Seconds())
for _, answer := range msg.Answer {
answer.Header().Ttl = ttl
}
}
return
}
defer func() {
if msg != nil {
@ -148,6 +155,11 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
}
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ip = net.ParseIP(host)
if ip != nil {
return ip, nil
}
query := &D.Msg{}
dnsType := D.TypeA
if r.ipv6 {
@ -207,6 +219,10 @@ func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result {
return ch
}
func (r *Resolver) IsMapping() bool {
return r.mapping
}
type NameServer struct {
Net string
Addr string
@ -244,8 +260,6 @@ func New(config Config) *Resolver {
mmdb, _ = geoip2.Open(C.Path.MMDB())
})
println(config.EnhancedMode)
r := &Resolver{
main: transform(config.Main),
ipv6: config.IPv6,

12
go.mod
View File

@ -1,18 +1,18 @@
module github.com/Dreamacro/clash
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.2
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112
github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v3.3.3+incompatible
github.com/go-chi/chi v4.0.1+incompatible
github.com/go-chi/cors v1.0.0
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.1.0+incompatible
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.0
github.com/miekg/dns v1.1.0
github.com/miekg/dns v1.1.4
github.com/oschwald/geoip2-golang v1.2.1
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/sirupsen/logrus v1.2.0
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
github.com/sirupsen/logrus v1.3.0
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
gopkg.in/eapache/channels.v1 v1.1.0

24
go.sum
View File

@ -1,39 +1,39 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.2 h1:8KgWbwAw5PJF+i6F3tI2iW/Em9WDtAuDG4obot8bGLM=
github.com/Dreamacro/go-shadowsocks2 v0.1.2/go.mod h1:J5YbNUiKtaD7EJmQ4O9ruUTY9+IgrflPgm63K1nUE0I=
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 h1:1axYxE0ZLJy40+ulq46XQt7MaJDJr4iGer1NQz7jmKw=
github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8=
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.0.1+incompatible h1:RSRC5qmFPtO90t7pTL0DBMNpZFsb/sHF3RXVlDgFisA=
github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/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.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
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.0 h1:yv9O9RJbvVFkvW8PKYqp4x7HQkc5RWwmUY/L8MdUaIg=
github.com/miekg/dns v1.1.0/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=

View File

@ -27,7 +27,6 @@ func ApplyConfig(cfg *config.Config, force bool) {
}
updateProxies(cfg.Proxies)
updateRules(cfg.Rules)
updateGeneral(cfg.General)
updateDNS(cfg.DNS)
}
@ -56,7 +55,11 @@ func updateDNS(c *config.DNS) {
EnhancedMode: c.EnhancedMode,
})
T.Instance().SetResolver(r)
dns.ReCreateServer(c.Listen, r)
if err := dns.ReCreateServer(c.Listen, r); err != nil {
log.Errorln("Start DNS server error: %s", err.Error())
return
}
log.Infoln("DNS server listening at: %s", c.Listen)
}
func updateProxies(proxies map[string]C.Proxy) {

View File

@ -12,6 +12,10 @@ func Parse() error {
return err
}
if cfg.General.ExternalUI != "" {
route.SetUIPath(cfg.General.ExternalUI)
}
if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.Secret)
}

View File

@ -23,7 +23,7 @@ func configRouter() http.Handler {
type configSchema struct {
Port *int `json:"port"`
SocksPort *int `json:"socket-port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
AllowLan *bool `json:"allow-lan"`
Mode *T.Mode `json:"mode"`
@ -32,7 +32,7 @@ type configSchema struct {
func getConfigs(w http.ResponseWriter, r *http.Request) {
general := executor.GetGeneral()
render.Respond(w, r, general)
render.JSON(w, r, general)
}
func pointerOrDefault(p *int, def int) int {
@ -46,8 +46,8 @@ func pointerOrDefault(p *int, def int) int {
func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
@ -68,7 +68,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
log.SetLevel(*general.LogLevel)
}
w.WriteHeader(http.StatusNoContent)
render.NoContent(w, r)
}
type updateConfigRequest struct {
@ -78,25 +78,25 @@ type updateConfigRequest struct {
func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := updateConfigRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
if !filepath.IsAbs(req.Path) {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, newError("path is not a absoluted path"))
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absoluted path"))
return
}
force := r.URL.Query().Get("force") == "true"
cfg, err := executor.ParseWithPath(req.Path)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, newError(err.Error()))
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(err.Error()))
return
}
executor.ApplyConfig(cfg, force)
w.WriteHeader(http.StatusNoContent)
render.NoContent(w, r)
}

View File

@ -47,8 +47,8 @@ func findProxyByName(next http.Handler) http.Handler {
proxies := T.Instance().Proxies()
proxy, exist := proxies[name]
if !exist {
w.WriteHeader(http.StatusNotFound)
render.Respond(w, r, ErrNotFound)
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
return
}
@ -59,14 +59,14 @@ func findProxyByName(next http.Handler) http.Handler {
func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := T.Instance().Proxies()
render.Respond(w, r, map[string]map[string]C.Proxy{
render.JSON(w, r, map[string]map[string]C.Proxy{
"proxies": proxies,
})
}
func getProxy(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
render.Respond(w, r, proxy)
render.JSON(w, r, proxy)
}
type UpdateProxyRequest struct {
@ -76,8 +76,8 @@ type UpdateProxyRequest struct {
func updateProxy(w http.ResponseWriter, r *http.Request) {
req := UpdateProxyRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
@ -85,18 +85,18 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
selector, ok := proxy.(*A.Selector)
if !ok {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
if err := selector.Set(req.Name); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
return
}
w.WriteHeader(http.StatusNoContent)
render.NoContent(w, r)
}
func getProxyDelay(w http.ResponseWriter, r *http.Request) {
@ -104,8 +104,8 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
@ -122,14 +122,14 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
select {
case <-time.After(time.Millisecond * time.Duration(timeout)):
w.WriteHeader(http.StatusRequestTimeout)
render.Respond(w, r, ErrRequestTimeout)
render.Status(r, http.StatusRequestTimeout)
render.JSON(w, r, ErrRequestTimeout)
case t := <-sigCh:
if t == 0 {
w.WriteHeader(http.StatusServiceUnavailable)
render.Respond(w, r, newError("An error occurred in the delay test"))
render.Status(r, http.StatusServiceUnavailable)
render.JSON(w, r, newError("An error occurred in the delay test"))
} else {
render.Respond(w, r, map[string]int16{
render.JSON(w, r, map[string]int16{
"delay": t,
})
}

View File

@ -33,8 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
})
}
w.WriteHeader(http.StatusOK)
render.Respond(w, r, map[string][]Rule{
render.JSON(w, r, map[string][]Rule{
"rules": rules,
})
}

View File

@ -17,6 +17,8 @@ import (
var (
serverSecret = ""
serverAddr = ""
uiPath = ""
)
type Traffic struct {
@ -24,6 +26,10 @@ type Traffic struct {
Down int64 `json:"down"`
}
func SetUIPath(path string) {
uiPath = path
}
func Start(addr string, secret string) {
if serverAddr != "" {
return
@ -36,18 +42,34 @@ func Start(addr string, secret string) {
cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
MaxAge: 300,
})
r.Use(cors.Handler, authentication)
root := chi.NewRouter().With(jsonContentType)
root.Get("/traffic", traffic)
root.Get("/logs", getLogs)
r.With(jsonContentType).Get("/traffic", traffic)
r.With(jsonContentType).Get("/logs", getLogs)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter())
r.Get("/", hello)
r.Group(func(r chi.Router) {
r.Use(cors.Handler, authentication)
r.Mount("/", root)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter())
})
if uiPath != "" {
r.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
log.Infoln("RESTful API listening at: %s", addr)
err := http.ListenAndServe(addr, r)
@ -77,8 +99,8 @@ func authentication(next http.Handler) http.Handler {
hasUnvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret {
w.WriteHeader(http.StatusUnauthorized)
render.Respond(w, r, ErrUnauthorized)
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
@ -86,8 +108,12 @@ func authentication(next http.Handler) http.Handler {
return http.HandlerFunc(fn)
}
func hello(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"hello": "clash"})
}
func traffic(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
render.Status(r, http.StatusOK)
tick := time.NewTicker(time.Second)
t := T.Instance().Traffic()
@ -116,8 +142,8 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
level, ok := log.LogLevelMapping[levelText]
if !ok {
w.WriteHeader(http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest)
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}

View File

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

View File

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

View File

@ -5,11 +5,11 @@ import (
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
adapters "github.com/Dreamacro/clash/adapters/inbound"
)
const (
@ -21,12 +21,17 @@ const (
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
conn := newTrafficTrack(outbound, t.traffic)
req := request.R
host := req.Host
keepalive := true
for {
if strings.ToLower(req.Header.Get("Connection")) == "close" {
keepalive = false
}
req.Header.Set("Connection", "close")
req.RequestURI = ""
adapters.RemoveHopByHopHeaders(req.Header)
@ -53,6 +58,10 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
break
}
if !keepalive {
break
}
req, err = http.ReadRequest(bufio.NewReader(request.Conn()))
if err != nil {
break
@ -66,8 +75,8 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
}
}
func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) {
conn := newTrafficTrack(proxy.Conn(), t.traffic)
func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, outbound net.Conn) {
conn := newTrafficTrack(outbound, t.traffic)
relay(request.Conn(), conn)
}

View File

@ -1,6 +1,7 @@
package tunnel
import (
"fmt"
"net"
"sync"
"time"
@ -10,7 +11,7 @@ import (
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
"gopkg.in/eapache/channels.v1"
channels "gopkg.in/eapache/channels.v1"
)
var (
@ -80,6 +81,10 @@ func (t *Tunnel) SetResolver(resolver *dns.Resolver) {
t.resolver = resolver
}
func (t *Tunnel) hasResolver() bool {
return t.resolver != nil
}
func (t *Tunnel) process() {
queue := t.queue.Out()
for {
@ -102,26 +107,20 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) {
return t.resolver.ResolveIP(host)
}
func (t *Tunnel) needLookupIP() bool {
return t.hasResolver() && t.resolver.IsMapping()
}
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
defer localConn.Close()
metadata := localConn.Metadata()
if metadata.Source == C.REDIR && t.resolver != nil {
if t.needLookupIP() {
host, exist := t.resolver.IPToHost(*metadata.IP)
if exist {
metadata.Host = host
metadata.AddrType = C.AtypDomainName
}
} else if metadata.IP == nil && metadata.AddrType == C.AtypDomainName {
ip, err := t.resolveIP(metadata.Host)
if err != nil {
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
} else {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.IP = &ip
}
} else {
log.Debugln("[DNS] unknown%#v", metadata)
}
var proxy C.Proxy
@ -132,11 +131,21 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
proxy = t.proxies["GLOBAL"]
// Rule
default:
proxy = t.match(metadata)
var err error
proxy, err = t.match(metadata)
if err != nil {
return
}
}
if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
remoConn, err := proxy.Generator(metadata)
if err != nil {
log.Warnln("Proxy[%s] connect [%s] error: %s", proxy.Name(), metadata.String(), err.Error())
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error())
return
}
defer remoConn.Close()
@ -149,22 +158,33 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
}
}
func (t *Tunnel) match(metadata *C.Metadata) C.Proxy {
func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.IP == nil
}
func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) {
t.configLock.RLock()
defer t.configLock.RUnlock()
for _, rule := range t.rules {
if rule.IsMatch(metadata) {
a, ok := t.proxies[rule.Adapter()]
if !ok {
continue
if t.shouldResolveIP(rule, metadata) {
ip, err := t.resolveIP(metadata.Host)
if err != nil {
return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error())
}
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.IP = &ip
}
if rule.IsMatch(metadata) {
if a, ok := t.proxies[rule.Adapter()]; ok {
log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter())
return a, nil
}
log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter())
return a
}
}
log.Infoln("%v doesn't match any rule using DIRECT", metadata.String())
return t.proxies["DIRECT"]
log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String())
return t.proxies["DIRECT"], nil
}
func newTunnel() *Tunnel {