├── docs └── logo.png ├── common ├── observable │ ├── iterable.go │ ├── subscriber.go │ ├── observable.go │ └── observable_test.go ├── sockopt │ ├── reuseaddr_other.go │ └── reuseaddr_linux.go ├── pool │ ├── pool.go │ ├── alloc_test.go │ └── alloc.go ├── picker │ ├── picker_test.go │ └── picker.go ├── singledo │ ├── singledo.go │ └── singledo_test.go ├── murmur3 │ ├── murmur.go │ └── murmur32.go ├── queue │ └── queue.go ├── cache │ ├── cache_test.go │ └── cache.go └── structure │ └── structure_test.go ├── constant ├── version.go ├── rule.go ├── path.go └── metadata.go ├── component ├── ssr │ ├── obfs │ │ ├── http_post.go │ │ ├── base.go │ │ ├── plain.go │ │ ├── obfs.go │ │ ├── stream.go │ │ └── random_head.go │ ├── protocol │ │ ├── base.go │ │ ├── auth_aes128_sha1.go │ │ ├── origin.go │ │ ├── packet.go │ │ ├── stream.go │ │ ├── auth_chain_b.go │ │ └── protocol.go │ └── tools │ │ └── encrypt.go ├── trie │ ├── node.go │ ├── domain_test.go │ └── domain.go ├── vmess │ ├── tls.go │ ├── user.go │ ├── http.go │ ├── chunk.go │ ├── vmess.go │ └── aead.go ├── snell │ ├── cipher.go │ └── snell.go ├── nat │ └── table.go ├── mmdb │ └── mmdb.go ├── auth │ └── auth.go ├── v2ray-plugin │ └── websocket.go ├── fakeip │ ├── pool_test.go │ └── pool.go ├── simple-obfs │ └── http.go └── resolver │ └── resolver.go ├── proxy ├── redir │ ├── tcp_windows.go │ ├── utils_other.go │ ├── tcp_linux_other.go │ ├── udp_other.go │ ├── tcp_linux_386.go │ ├── utils.go │ ├── tcp.go │ ├── tcp_linux.go │ ├── tcp_freebsd.go │ ├── udp.go │ ├── tcp_darwin.go │ ├── udp_linux.go │ └── utils_linux.go ├── auth │ └── auth.go ├── socks │ ├── utils.go │ ├── tcp.go │ └── udp.go ├── mixed │ ├── conn.go │ └── mixed.go └── http │ └── server.go ├── rules ├── process_other.go ├── base.go ├── final.go ├── domain.go ├── domain_keyword.go ├── domain_suffix.go ├── port.go ├── geoip.go ├── parser.go └── ipcidr.go ├── .gitignore ├── hub ├── route │ ├── ctxkeys.go │ ├── common.go │ ├── errors.go │ ├── rules.go │ ├── connections.go │ ├── provider.go │ └── configs.go └── hub.go ├── adapters ├── outboundgroup │ ├── common.go │ ├── util.go │ ├── fallback.go │ ├── selector.go │ ├── relay.go │ ├── loadbalance.go │ └── urltest.go ├── inbound │ ├── https.go │ ├── packet.go │ ├── socket.go │ ├── http.go │ └── util.go ├── outbound │ ├── direct.go │ ├── reject.go │ ├── parser.go │ ├── snell.go │ ├── util.go │ ├── trojan.go │ └── http.go └── provider │ ├── healthcheck.go │ ├── parser.go │ └── vehicle.go ├── .github ├── workflows │ ├── stale.yml │ ├── go.yml │ └── docker.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── Dockerfile ├── dns ├── filters.go ├── client.go ├── server.go └── doh.go ├── go.mod ├── tunnel ├── mode.go ├── manager.go └── tracker.go ├── log ├── level.go └── log.go ├── config └── initial.go ├── README.md ├── Makefile └── main.go /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjtug/clash/master/docs/logo.png -------------------------------------------------------------------------------- /common/observable/iterable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | type Iterable <-chan interface{} 4 | -------------------------------------------------------------------------------- /constant/version.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var ( 4 | Version = "unknown version" 5 | BuildTime = "unknown time" 6 | ) 7 | -------------------------------------------------------------------------------- /common/sockopt/reuseaddr_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package sockopt 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | func UDPReuseaddr(c *net.UDPConn) (err error) { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /component/ssr/obfs/http_post.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | func init() { 4 | register("http_post", newHTTPPost) 5 | } 6 | 7 | func newHTTPPost(b *Base) Obfs { 8 | return &httpObfs{Base: b, post: true} 9 | } 10 | -------------------------------------------------------------------------------- /component/ssr/obfs/base.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | // Base information for obfs 4 | type Base struct { 5 | IVSize int 6 | Key []byte 7 | HeadLen int 8 | Host string 9 | Port int 10 | Param string 11 | } 12 | -------------------------------------------------------------------------------- /component/ssr/protocol/base.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // Base information for protocol 4 | type Base struct { 5 | IV []byte 6 | Key []byte 7 | TCPMss int 8 | Overhead int 9 | Param string 10 | } 11 | -------------------------------------------------------------------------------- /proxy/redir/tcp_windows.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/component/socks5" 8 | ) 9 | 10 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 11 | return nil, errors.New("Windows not support yet") 12 | } 13 | -------------------------------------------------------------------------------- /proxy/redir/utils_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { 11 | return nil, errors.New("UDP redir not supported on current platform") 12 | } 13 | -------------------------------------------------------------------------------- /rules/process_other.go: -------------------------------------------------------------------------------- 1 | // +build !darwin,!linux,!windows 2 | // +build !freebsd !amd64 3 | 4 | package rules 5 | 6 | import ( 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | func NewProcess(process string, adapter string) (C.Rule, error) { 11 | return nil, ErrPlatformNotSupport 12 | } 13 | -------------------------------------------------------------------------------- /proxy/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/auth" 5 | ) 6 | 7 | var ( 8 | authenticator auth.Authenticator 9 | ) 10 | 11 | func Authenticator() auth.Authenticator { 12 | return authenticator 13 | } 14 | 15 | func SetAuthenticator(au auth.Authenticator) { 16 | authenticator = au 17 | } 18 | -------------------------------------------------------------------------------- /proxy/redir/tcp_linux_other.go: -------------------------------------------------------------------------------- 1 | // +build linux,!386 2 | 3 | package redir 4 | 5 | import "syscall" 6 | 7 | const GETSOCKOPT = syscall.SYS_GETSOCKOPT 8 | 9 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 10 | if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { 11 | return errno 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # dep 16 | vendor 17 | 18 | # GoLand 19 | .idea/* 20 | 21 | # macOS file 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /hub/route/ctxkeys.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | var ( 4 | CtxKeyProxyName = contextKey("proxy name") 5 | CtxKeyProviderName = contextKey("provider name") 6 | CtxKeyProxy = contextKey("proxy") 7 | CtxKeyProvider = contextKey("provider") 8 | ) 9 | 10 | type contextKey string 11 | 12 | func (c contextKey) String() string { 13 | return "clash context key " + string(c) 14 | } 15 | -------------------------------------------------------------------------------- /common/sockopt/reuseaddr_linux.go: -------------------------------------------------------------------------------- 1 | package sockopt 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | ) 7 | 8 | func UDPReuseaddr(c *net.UDPConn) (err error) { 9 | rc, err := c.SyscallConn() 10 | if err != nil { 11 | return 12 | } 13 | 14 | rc.Control(func(fd uintptr) { 15 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 16 | }) 17 | 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /proxy/redir/udp_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func setsockopt(c *net.UDPConn, addr string) error { 11 | return errors.New("UDP redir not supported on current platform") 12 | } 13 | 14 | func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { 15 | return nil, errors.New("UDP redir not supported on current platform") 16 | } 17 | -------------------------------------------------------------------------------- /hub/route/common.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | 7 | "github.com/go-chi/chi" 8 | ) 9 | 10 | // When name is composed of a partial escape string, Golang does not unescape it 11 | func getEscapeParam(r *http.Request, paramName string) string { 12 | param := chi.URLParam(r, paramName) 13 | if newParam, err := url.PathUnescape(param); err == nil { 14 | param = newParam 15 | } 16 | return param 17 | } 18 | -------------------------------------------------------------------------------- /common/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | const ( 4 | // io.Copy default buffer size is 32 KiB 5 | // but the maximum packet size of vmess/shadowsocks is about 16 KiB 6 | // so define a buffer of 20 KiB to reduce the memory of each TCP relay 7 | RelayBufferSize = 20 * 1024 8 | ) 9 | 10 | func Get(size int) []byte { 11 | return defaultAllocator.Get(size) 12 | } 13 | 14 | func Put(buf []byte) error { 15 | return defaultAllocator.Put(buf) 16 | } 17 | -------------------------------------------------------------------------------- /adapters/outboundgroup/common.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Dreamacro/clash/adapters/provider" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | const ( 11 | defaultGetProxiesDuration = time.Second * 5 12 | ) 13 | 14 | func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy { 15 | proxies := []C.Proxy{} 16 | for _, provider := range providers { 17 | proxies = append(proxies, provider.Proxies()...) 18 | } 19 | return proxies 20 | } 21 | -------------------------------------------------------------------------------- /component/ssr/obfs/plain.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | type plain struct{} 4 | 5 | func init() { 6 | register("plain", newPlain) 7 | } 8 | 9 | func newPlain(b *Base) Obfs { 10 | return &plain{} 11 | } 12 | 13 | func (p *plain) initForConn() Obfs { return &plain{} } 14 | 15 | func (p *plain) GetObfsOverhead() int { 16 | return 0 17 | } 18 | 19 | func (p *plain) Encode(b []byte) ([]byte, error) { 20 | return b, nil 21 | } 22 | 23 | func (p *plain) Decode(b []byte) ([]byte, bool, error) { 24 | return b, false, nil 25 | } 26 | -------------------------------------------------------------------------------- /proxy/redir/tcp_linux_386.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 9 | 10 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 11 | var a [6]uintptr 12 | a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 13 | if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { 14 | return errno 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /rules/base.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | errPayload = errors.New("payload error") 9 | errParams = errors.New("params error") 10 | ErrPlatformNotSupport = errors.New("not support on this platform") 11 | ErrInvalidNetwork = errors.New("invalid network") 12 | 13 | noResolve = "no-resolve" 14 | ) 15 | 16 | func HasNoResolve(params []string) bool { 17 | for _, p := range params { 18 | if p == noResolve { 19 | return true 20 | } 21 | } 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /component/ssr/protocol/auth_aes128_sha1.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/Dreamacro/clash/component/ssr/tools" 7 | ) 8 | 9 | func init() { 10 | register("auth_aes128_sha1", newAuthAES128SHA1) 11 | } 12 | 13 | func newAuthAES128SHA1(b *Base) Protocol { 14 | return &authAES128{ 15 | Base: b, 16 | recvInfo: &recvInfo{buffer: new(bytes.Buffer)}, 17 | authData: &authData{}, 18 | salt: "auth_aes128_sha1", 19 | hmac: tools.HmacSHA1, 20 | hashDigest: tools.SHA1Sum, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Mark stale issues and pull requests 3 | 4 | on: 5 | schedule: 6 | - cron: "30 1 * * *" 7 | 8 | jobs: 9 | stale: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/stale@v1 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' 18 | days-before-stale: 120 19 | days-before-close: 5 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | RUN apk add --no-cache make git && \ 4 | wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb 5 | WORKDIR /clash-src 6 | COPY --from=tonistiigi/xx:golang / / 7 | COPY . /clash-src 8 | RUN go mod download && \ 9 | make docker && \ 10 | mv ./bin/clash-docker /clash 11 | 12 | FROM alpine:latest 13 | 14 | RUN apk add --no-cache ca-certificates 15 | COPY --from=builder /Country.mmdb /root/.config/clash/ 16 | COPY --from=builder /clash / 17 | ENTRYPOINT ["/clash"] 18 | -------------------------------------------------------------------------------- /adapters/inbound/https.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | // NewHTTPS is HTTPAdapter generator 11 | func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { 12 | metadata := parseHTTPAddr(request) 13 | metadata.Type = C.HTTPCONNECT 14 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 15 | metadata.SrcIP = ip 16 | metadata.SrcPort = port 17 | } 18 | return &SocketAdapter{ 19 | metadata: metadata, 20 | Conn: conn, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /component/trie/node.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | // Node is the trie's node 4 | type Node struct { 5 | Data interface{} 6 | children map[string]*Node 7 | } 8 | 9 | func (n *Node) getChild(s string) *Node { 10 | return n.children[s] 11 | } 12 | 13 | func (n *Node) hasChild(s string) bool { 14 | return n.getChild(s) != nil 15 | } 16 | 17 | func (n *Node) addChild(s string, child *Node) { 18 | n.children[s] = child 19 | } 20 | 21 | func newNode(data interface{}) *Node { 22 | return &Node{ 23 | Data: data, 24 | children: map[string]*Node{}, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dns/filters.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/component/mmdb" 7 | ) 8 | 9 | type fallbackFilter interface { 10 | Match(net.IP) bool 11 | } 12 | 13 | type geoipFilter struct{} 14 | 15 | func (gf *geoipFilter) Match(ip net.IP) bool { 16 | record, _ := mmdb.Instance().Country(ip) 17 | return record.Country.IsoCode != "CN" && record.Country.IsoCode != "" 18 | } 19 | 20 | type ipnetFilter struct { 21 | ipnet *net.IPNet 22 | } 23 | 24 | func (inf *ipnetFilter) Match(ip net.IP) bool { 25 | return inf.ipnet.Contains(ip) 26 | } 27 | -------------------------------------------------------------------------------- /component/vmess/tls.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | ) 7 | 8 | type TLSConfig struct { 9 | Host string 10 | SkipCertVerify bool 11 | SessionCache tls.ClientSessionCache 12 | } 13 | 14 | func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { 15 | tlsConfig := &tls.Config{ 16 | ServerName: cfg.Host, 17 | InsecureSkipVerify: cfg.SkipCertVerify, 18 | ClientSessionCache: cfg.SessionCache, 19 | } 20 | 21 | tlsConn := tls.Client(conn, tlsConfig) 22 | err := tlsConn.Handshake() 23 | return tlsConn, err 24 | } 25 | -------------------------------------------------------------------------------- /hub/route/errors.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | var ( 4 | ErrUnauthorized = newError("Unauthorized") 5 | ErrBadRequest = newError("Body invalid") 6 | ErrForbidden = newError("Forbidden") 7 | ErrNotFound = newError("Resource not found") 8 | ErrRequestTimeout = newError("Timeout") 9 | ) 10 | 11 | // HTTPError is custom HTTP error for API 12 | type HTTPError struct { 13 | Message string `json:"message"` 14 | } 15 | 16 | func (e *HTTPError) Error() string { 17 | return e.Message 18 | } 19 | 20 | func newError(msg string) *HTTPError { 21 | return &HTTPError{Message: msg} 22 | } 23 | -------------------------------------------------------------------------------- /rules/final.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | type Match struct { 8 | adapter string 9 | } 10 | 11 | func (f *Match) RuleType() C.RuleType { 12 | return C.MATCH 13 | } 14 | 15 | func (f *Match) Match(metadata *C.Metadata) bool { 16 | return true 17 | } 18 | 19 | func (f *Match) Adapter() string { 20 | return f.adapter 21 | } 22 | 23 | func (f *Match) Payload() string { 24 | return "" 25 | } 26 | 27 | func (f *Match) ShouldResolveIP() bool { 28 | return false 29 | } 30 | 31 | func NewMatch(adapter string) *Match { 32 | return &Match{ 33 | adapter: adapter, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /component/snell/cipher.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "crypto/cipher" 5 | 6 | "golang.org/x/crypto/argon2" 7 | ) 8 | 9 | type snellCipher struct { 10 | psk []byte 11 | makeAEAD func(key []byte) (cipher.AEAD, error) 12 | } 13 | 14 | func (sc *snellCipher) KeySize() int { return 32 } 15 | func (sc *snellCipher) SaltSize() int { return 16 } 16 | func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { 17 | return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) 18 | } 19 | func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { 20 | return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) 21 | } 22 | -------------------------------------------------------------------------------- /component/ssr/tools/encrypt.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | ) 8 | 9 | const HmacSHA1Len = 10 10 | 11 | func HmacMD5(key, data []byte) []byte { 12 | hmacMD5 := hmac.New(md5.New, key) 13 | hmacMD5.Write(data) 14 | return hmacMD5.Sum(nil)[:16] 15 | } 16 | 17 | func HmacSHA1(key, data []byte) []byte { 18 | hmacSHA1 := hmac.New(sha1.New, key) 19 | hmacSHA1.Write(data) 20 | return hmacSHA1.Sum(nil)[:20] 21 | } 22 | 23 | func MD5Sum(b []byte) []byte { 24 | h := md5.New() 25 | h.Write(b) 26 | return h.Sum(nil) 27 | } 28 | 29 | func SHA1Sum(b []byte) []byte { 30 | h := sha1.New() 31 | h.Write(b) 32 | return h.Sum(nil) 33 | } 34 | -------------------------------------------------------------------------------- /common/observable/subscriber.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "sync" 5 | 6 | "gopkg.in/eapache/channels.v1" 7 | ) 8 | 9 | type Subscription <-chan interface{} 10 | 11 | type Subscriber struct { 12 | buffer *channels.InfiniteChannel 13 | once sync.Once 14 | } 15 | 16 | func (s *Subscriber) Emit(item interface{}) { 17 | s.buffer.In() <- item 18 | } 19 | 20 | func (s *Subscriber) Out() Subscription { 21 | return s.buffer.Out() 22 | } 23 | 24 | func (s *Subscriber) Close() { 25 | s.once.Do(func() { 26 | s.buffer.Close() 27 | }) 28 | } 29 | 30 | func newSubscriber() *Subscriber { 31 | sub := &Subscriber{ 32 | buffer: channels.NewInfiniteChannel(), 33 | } 34 | return sub 35 | } 36 | -------------------------------------------------------------------------------- /component/nat/table.go: -------------------------------------------------------------------------------- 1 | package nat 2 | 3 | import ( 4 | "sync" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Table struct { 10 | mapping sync.Map 11 | } 12 | 13 | func (t *Table) Set(key string, pc C.PacketConn) { 14 | t.mapping.Store(key, pc) 15 | } 16 | 17 | func (t *Table) Get(key string) C.PacketConn { 18 | item, exist := t.mapping.Load(key) 19 | if !exist { 20 | return nil 21 | } 22 | return item.(C.PacketConn) 23 | } 24 | 25 | func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { 26 | item, loaded := t.mapping.LoadOrStore(key, &sync.WaitGroup{}) 27 | return item.(*sync.WaitGroup), loaded 28 | } 29 | 30 | func (t *Table) Delete(key string) { 31 | t.mapping.Delete(key) 32 | } 33 | 34 | // New return *Cache 35 | func New() *Table { 36 | return &Table{} 37 | } 38 | -------------------------------------------------------------------------------- /proxy/redir/utils.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | type packet struct { 10 | lAddr *net.UDPAddr 11 | buf []byte 12 | } 13 | 14 | func (c *packet) Data() []byte { 15 | return c.buf 16 | } 17 | 18 | // WriteBack opens a new socket binding `addr` to wirte UDP packet back 19 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 20 | tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) 21 | if err != nil { 22 | n = 0 23 | return 24 | } 25 | n, err = tc.Write(b) 26 | tc.Close() 27 | return 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *packet) LocalAddr() net.Addr { 32 | return c.lAddr 33 | } 34 | 35 | func (c *packet) Drop() { 36 | pool.Put(c.buf) 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /component/ssr/protocol/origin.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type origin struct{ *Base } 4 | 5 | func init() { 6 | register("origin", newOrigin) 7 | } 8 | 9 | func newOrigin(b *Base) Protocol { 10 | return &origin{} 11 | } 12 | 13 | func (o *origin) initForConn(iv []byte) Protocol { return &origin{} } 14 | 15 | func (o *origin) GetProtocolOverhead() int { 16 | return 0 17 | } 18 | 19 | func (o *origin) SetOverhead(overhead int) { 20 | } 21 | 22 | func (o *origin) Decode(b []byte) ([]byte, int, error) { 23 | return b, len(b), nil 24 | } 25 | 26 | func (o *origin) Encode(b []byte) ([]byte, error) { 27 | return b, nil 28 | } 29 | 30 | func (o *origin) DecodePacket(b []byte) ([]byte, int, error) { 31 | return b, len(b), nil 32 | } 33 | 34 | func (o *origin) EncodePacket(b []byte) ([]byte, error) { 35 | return b, nil 36 | } 37 | -------------------------------------------------------------------------------- /hub/route/rules.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Dreamacro/clash/tunnel" 7 | 8 | "github.com/go-chi/chi" 9 | "github.com/go-chi/render" 10 | ) 11 | 12 | func ruleRouter() http.Handler { 13 | r := chi.NewRouter() 14 | r.Get("/", getRules) 15 | return r 16 | } 17 | 18 | type Rule struct { 19 | Type string `json:"type"` 20 | Payload string `json:"payload"` 21 | Proxy string `json:"proxy"` 22 | } 23 | 24 | func getRules(w http.ResponseWriter, r *http.Request) { 25 | rawRules := tunnel.Rules() 26 | 27 | rules := []Rule{} 28 | for _, rule := range rawRules { 29 | rules = append(rules, Rule{ 30 | Type: rule.RuleType().String(), 31 | Payload: rule.Payload(), 32 | Proxy: rule.Adapter(), 33 | }) 34 | } 35 | 36 | render.JSON(w, r, render.M{ 37 | "rules": rules, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /rules/domain.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Domain struct { 10 | domain string 11 | adapter string 12 | } 13 | 14 | func (d *Domain) RuleType() C.RuleType { 15 | return C.Domain 16 | } 17 | 18 | func (d *Domain) Match(metadata *C.Metadata) bool { 19 | if metadata.AddrType != C.AtypDomainName { 20 | return false 21 | } 22 | return metadata.Host == d.domain 23 | } 24 | 25 | func (d *Domain) Adapter() string { 26 | return d.adapter 27 | } 28 | 29 | func (d *Domain) Payload() string { 30 | return d.domain 31 | } 32 | 33 | func (d *Domain) ShouldResolveIP() bool { 34 | return false 35 | } 36 | 37 | func NewDomain(domain string, adapter string) *Domain { 38 | return &Domain{ 39 | domain: strings.ToLower(domain), 40 | adapter: adapter, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Dreamacro/clash 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a 7 | github.com/eapache/queue v1.1.0 // indirect 8 | github.com/go-chi/chi v4.1.2+incompatible 9 | github.com/go-chi/cors v1.1.1 10 | github.com/go-chi/render v1.0.1 11 | github.com/gofrs/uuid v3.3.0+incompatible 12 | github.com/gorilla/websocket v1.4.2 13 | github.com/miekg/dns v1.1.29 14 | github.com/oschwald/geoip2-golang v1.4.0 15 | github.com/sirupsen/logrus v1.6.0 16 | github.com/stretchr/testify v1.6.1 17 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 18 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9 19 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a 20 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd 21 | gopkg.in/eapache/channels.v1 v1.1.0 22 | gopkg.in/yaml.v2 v2.3.0 23 | ) 24 | -------------------------------------------------------------------------------- /proxy/socks/utils.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | "github.com/Dreamacro/clash/component/socks5" 8 | ) 9 | 10 | type packet struct { 11 | pc net.PacketConn 12 | rAddr net.Addr 13 | payload []byte 14 | bufRef []byte 15 | } 16 | 17 | func (c *packet) Data() []byte { 18 | return c.payload 19 | } 20 | 21 | // WriteBack wirtes UDP packet with source(ip, port) = `addr` 22 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 23 | packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) 24 | if err != nil { 25 | return 26 | } 27 | return c.pc.WriteTo(packet, c.rAddr) 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *packet) LocalAddr() net.Addr { 32 | return c.rAddr 33 | } 34 | 35 | func (c *packet) Drop() { 36 | pool.Put(c.bufRef) 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /component/mmdb/mmdb.go: -------------------------------------------------------------------------------- 1 | package mmdb 2 | 3 | import ( 4 | "sync" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/log" 8 | 9 | "github.com/oschwald/geoip2-golang" 10 | ) 11 | 12 | var mmdb *geoip2.Reader 13 | var once sync.Once 14 | 15 | func LoadFromBytes(buffer []byte) { 16 | once.Do(func() { 17 | var err error 18 | mmdb, err = geoip2.FromBytes(buffer) 19 | if err != nil { 20 | log.Fatalln("Can't load mmdb: %s", err.Error()) 21 | } 22 | }) 23 | } 24 | 25 | func Verify() bool { 26 | instance, err := geoip2.Open(C.Path.MMDB()) 27 | if err == nil { 28 | instance.Close() 29 | } 30 | return err == nil 31 | } 32 | 33 | func Instance() *geoip2.Reader { 34 | once.Do(func() { 35 | var err error 36 | mmdb, err = geoip2.Open(C.Path.MMDB()) 37 | if err != nil { 38 | log.Fatalln("Can't load mmdb: %s", err.Error()) 39 | } 40 | }) 41 | 42 | return mmdb 43 | } 44 | -------------------------------------------------------------------------------- /proxy/mixed/conn.go: -------------------------------------------------------------------------------- 1 | package mixed 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | ) 7 | 8 | type BufferedConn struct { 9 | r *bufio.Reader 10 | net.Conn 11 | } 12 | 13 | func NewBufferedConn(c net.Conn) *BufferedConn { 14 | return &BufferedConn{bufio.NewReader(c), c} 15 | } 16 | 17 | // Reader returns the internal bufio.Reader. 18 | func (c *BufferedConn) Reader() *bufio.Reader { 19 | return c.r 20 | } 21 | 22 | // Peek returns the next n bytes without advancing the reader. 23 | func (c *BufferedConn) Peek(n int) ([]byte, error) { 24 | return c.r.Peek(n) 25 | } 26 | 27 | func (c *BufferedConn) Read(p []byte) (int, error) { 28 | return c.r.Read(p) 29 | } 30 | 31 | func (c *BufferedConn) ReadByte() (byte, error) { 32 | return c.r.ReadByte() 33 | } 34 | 35 | func (c *BufferedConn) UnreadByte() error { 36 | return c.r.UnreadByte() 37 | } 38 | 39 | func (c *BufferedConn) Buffered() int { 40 | return c.r.Buffered() 41 | } 42 | -------------------------------------------------------------------------------- /adapters/inbound/packet.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/socks5" 5 | C "github.com/Dreamacro/clash/constant" 6 | ) 7 | 8 | // PacketAdapter is a UDP Packet adapter for socks/redir/tun 9 | type PacketAdapter struct { 10 | C.UDPPacket 11 | metadata *C.Metadata 12 | } 13 | 14 | // Metadata returns destination metadata 15 | func (s *PacketAdapter) Metadata() *C.Metadata { 16 | return s.metadata 17 | } 18 | 19 | // NewPacket is PacketAdapter generator 20 | func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter { 21 | metadata := parseSocksAddr(target) 22 | metadata.NetWork = C.UDP 23 | metadata.Type = source 24 | if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil { 25 | metadata.SrcIP = ip 26 | metadata.SrcPort = port 27 | } 28 | 29 | return &PacketAdapter{ 30 | UDPPacket: packet, 31 | metadata: metadata, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /adapters/inbound/socket.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/component/socks5" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | // SocketAdapter is a adapter for socks and redir connection 11 | type SocketAdapter struct { 12 | net.Conn 13 | metadata *C.Metadata 14 | } 15 | 16 | // Metadata return destination metadata 17 | func (s *SocketAdapter) Metadata() *C.Metadata { 18 | return s.metadata 19 | } 20 | 21 | // NewSocket is SocketAdapter generator 22 | func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter { 23 | metadata := parseSocksAddr(target) 24 | metadata.NetWork = C.TCP 25 | metadata.Type = source 26 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 27 | metadata.SrcIP = ip 28 | metadata.SrcPort = port 29 | } 30 | 31 | return &SocketAdapter{ 32 | Conn: conn, 33 | metadata: metadata, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rules/domain_keyword.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainKeyword struct { 10 | keyword string 11 | adapter string 12 | } 13 | 14 | func (dk *DomainKeyword) RuleType() C.RuleType { 15 | return C.DomainKeyword 16 | } 17 | 18 | func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { 19 | if metadata.AddrType != C.AtypDomainName { 20 | return false 21 | } 22 | domain := metadata.Host 23 | return strings.Contains(domain, dk.keyword) 24 | } 25 | 26 | func (dk *DomainKeyword) Adapter() string { 27 | return dk.adapter 28 | } 29 | 30 | func (dk *DomainKeyword) Payload() string { 31 | return dk.keyword 32 | } 33 | 34 | func (dk *DomainKeyword) ShouldResolveIP() bool { 35 | return false 36 | } 37 | 38 | func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { 39 | return &DomainKeyword{ 40 | keyword: strings.ToLower(keyword), 41 | adapter: adapter, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rules/domain_suffix.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainSuffix struct { 10 | suffix string 11 | adapter string 12 | } 13 | 14 | func (ds *DomainSuffix) RuleType() C.RuleType { 15 | return C.DomainSuffix 16 | } 17 | 18 | func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { 19 | if metadata.AddrType != C.AtypDomainName { 20 | return false 21 | } 22 | domain := metadata.Host 23 | return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix 24 | } 25 | 26 | func (ds *DomainSuffix) Adapter() string { 27 | return ds.adapter 28 | } 29 | 30 | func (ds *DomainSuffix) Payload() string { 31 | return ds.suffix 32 | } 33 | 34 | func (ds *DomainSuffix) ShouldResolveIP() bool { 35 | return false 36 | } 37 | 38 | func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { 39 | return &DomainSuffix{ 40 | suffix: strings.ToLower(suffix), 41 | adapter: adapter, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /constant/rule.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // Rule Type 4 | const ( 5 | Domain RuleType = iota 6 | DomainSuffix 7 | DomainKeyword 8 | GEOIP 9 | IPCIDR 10 | SrcIPCIDR 11 | SrcPort 12 | DstPort 13 | Process 14 | MATCH 15 | ) 16 | 17 | type RuleType int 18 | 19 | func (rt RuleType) String() string { 20 | switch rt { 21 | case Domain: 22 | return "Domain" 23 | case DomainSuffix: 24 | return "DomainSuffix" 25 | case DomainKeyword: 26 | return "DomainKeyword" 27 | case GEOIP: 28 | return "GeoIP" 29 | case IPCIDR: 30 | return "IPCIDR" 31 | case SrcIPCIDR: 32 | return "SrcIPCIDR" 33 | case SrcPort: 34 | return "SrcPort" 35 | case DstPort: 36 | return "DstPort" 37 | case Process: 38 | return "Process" 39 | case MATCH: 40 | return "Match" 41 | default: 42 | return "Unknown" 43 | } 44 | } 45 | 46 | type Rule interface { 47 | RuleType() RuleType 48 | Match(metadata *Metadata) bool 49 | Adapter() string 50 | Payload() string 51 | ShouldResolveIP() bool 52 | } 53 | -------------------------------------------------------------------------------- /rules/port.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strconv" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Port struct { 10 | adapter string 11 | port string 12 | isSource bool 13 | } 14 | 15 | func (p *Port) RuleType() C.RuleType { 16 | if p.isSource { 17 | return C.SrcPort 18 | } 19 | return C.DstPort 20 | } 21 | 22 | func (p *Port) Match(metadata *C.Metadata) bool { 23 | if p.isSource { 24 | return metadata.SrcPort == p.port 25 | } 26 | return metadata.DstPort == p.port 27 | } 28 | 29 | func (p *Port) Adapter() string { 30 | return p.adapter 31 | } 32 | 33 | func (p *Port) Payload() string { 34 | return p.port 35 | } 36 | 37 | func (p *Port) ShouldResolveIP() bool { 38 | return false 39 | } 40 | 41 | func NewPort(port string, adapter string, isSource bool) (*Port, error) { 42 | _, err := strconv.Atoi(port) 43 | if err != nil { 44 | return nil, errPayload 45 | } 46 | return &Port{ 47 | adapter: adapter, 48 | port: port, 49 | isSource: isSource, 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /rules/geoip.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/mmdb" 5 | C "github.com/Dreamacro/clash/constant" 6 | ) 7 | 8 | type GEOIP struct { 9 | country string 10 | adapter string 11 | noResolveIP bool 12 | } 13 | 14 | func (g *GEOIP) RuleType() C.RuleType { 15 | return C.GEOIP 16 | } 17 | 18 | func (g *GEOIP) Match(metadata *C.Metadata) bool { 19 | ip := metadata.DstIP 20 | if ip == nil { 21 | return false 22 | } 23 | record, _ := mmdb.Instance().Country(ip) 24 | return record.Country.IsoCode == g.country 25 | } 26 | 27 | func (g *GEOIP) Adapter() string { 28 | return g.adapter 29 | } 30 | 31 | func (g *GEOIP) Payload() string { 32 | return g.country 33 | } 34 | 35 | func (g *GEOIP) ShouldResolveIP() bool { 36 | return !g.noResolveIP 37 | } 38 | 39 | func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { 40 | geoip := &GEOIP{ 41 | country: country, 42 | adapter: adapter, 43 | noResolveIP: noResolveIP, 44 | } 45 | 46 | return geoip 47 | } 48 | -------------------------------------------------------------------------------- /adapters/outbound/direct.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/component/dialer" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type Direct struct { 12 | *Base 13 | } 14 | 15 | func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 16 | address := net.JoinHostPort(metadata.String(), metadata.DstPort) 17 | 18 | c, err := dialer.DialContext(ctx, "tcp", address) 19 | if err != nil { 20 | return nil, err 21 | } 22 | tcpKeepAlive(c) 23 | return NewConn(c, d), nil 24 | } 25 | 26 | func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 27 | pc, err := dialer.ListenPacket("udp", "") 28 | if err != nil { 29 | return nil, err 30 | } 31 | return newPacketConn(&directPacketConn{pc}, d), nil 32 | } 33 | 34 | type directPacketConn struct { 35 | net.PacketConn 36 | } 37 | 38 | func NewDirect() *Direct { 39 | return &Direct{ 40 | Base: &Base{ 41 | name: "DIRECT", 42 | tp: C.Direct, 43 | udp: true, 44 | }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/picker/picker_test.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func sleepAndSend(ctx context.Context, delay int, input interface{}) func() (interface{}, error) { 12 | return func() (interface{}, error) { 13 | timer := time.NewTimer(time.Millisecond * time.Duration(delay)) 14 | select { 15 | case <-timer.C: 16 | return input, nil 17 | case <-ctx.Done(): 18 | return nil, ctx.Err() 19 | } 20 | } 21 | } 22 | 23 | func TestPicker_Basic(t *testing.T) { 24 | picker, ctx := WithContext(context.Background()) 25 | picker.Go(sleepAndSend(ctx, 30, 2)) 26 | picker.Go(sleepAndSend(ctx, 20, 1)) 27 | 28 | number := picker.Wait() 29 | assert.NotNil(t, number) 30 | assert.Equal(t, number.(int), 1) 31 | } 32 | 33 | func TestPicker_Timeout(t *testing.T) { 34 | picker, ctx := WithTimeout(context.Background(), time.Millisecond*5) 35 | picker.Go(sleepAndSend(ctx, 20, 1)) 36 | 37 | number := picker.Wait() 38 | assert.Nil(t, number) 39 | assert.NotNil(t, picker.Error()) 40 | } 41 | -------------------------------------------------------------------------------- /component/ssr/obfs/obfs.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") 11 | errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") 12 | errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") 13 | ) 14 | 15 | // Obfs provides methods for decoding and encoding 16 | type Obfs interface { 17 | initForConn() Obfs 18 | GetObfsOverhead() int 19 | Decode(b []byte) ([]byte, bool, error) 20 | Encode(b []byte) ([]byte, error) 21 | } 22 | 23 | type obfsCreator func(b *Base) Obfs 24 | 25 | var obfsList = make(map[string]obfsCreator) 26 | 27 | func register(name string, c obfsCreator) { 28 | obfsList[name] = c 29 | } 30 | 31 | // PickObfs returns an obfs of the given name 32 | func PickObfs(name string, b *Base) (Obfs, error) { 33 | if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok { 34 | return obfsCreator(b), nil 35 | } 36 | return nil, fmt.Errorf("Obfs %s not supported", name) 37 | } 38 | -------------------------------------------------------------------------------- /component/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Authenticator interface { 8 | Verify(user string, pass string) bool 9 | Users() []string 10 | } 11 | 12 | type AuthUser struct { 13 | User string 14 | Pass string 15 | } 16 | 17 | type inMemoryAuthenticator struct { 18 | storage *sync.Map 19 | usernames []string 20 | } 21 | 22 | func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { 23 | realPass, ok := au.storage.Load(user) 24 | return ok && realPass == pass 25 | } 26 | 27 | func (au *inMemoryAuthenticator) Users() []string { return au.usernames } 28 | 29 | func NewAuthenticator(users []AuthUser) Authenticator { 30 | if len(users) == 0 { 31 | return nil 32 | } 33 | 34 | au := &inMemoryAuthenticator{storage: &sync.Map{}} 35 | for _, user := range users { 36 | au.storage.Store(user.User, user.Pass) 37 | } 38 | usernames := make([]string, 0, len(users)) 39 | au.storage.Range(func(key, value interface{}) bool { 40 | usernames = append(usernames, key.(string)) 41 | return true 42 | }) 43 | au.usernames = usernames 44 | 45 | return au 46 | } 47 | -------------------------------------------------------------------------------- /component/ssr/protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | // NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding 10 | func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn { 11 | return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)} 12 | } 13 | 14 | // PacketConn represents a protocol packet connection 15 | type PacketConn struct { 16 | net.PacketConn 17 | Protocol 18 | } 19 | 20 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 21 | buf := pool.Get(pool.RelayBufferSize) 22 | defer pool.Put(buf) 23 | buf, err := c.EncodePacket(b) 24 | if err != nil { 25 | return 0, err 26 | } 27 | _, err = c.PacketConn.WriteTo(buf, addr) 28 | return len(b), err 29 | } 30 | 31 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 32 | n, addr, err := c.PacketConn.ReadFrom(b) 33 | if err != nil { 34 | return n, addr, err 35 | } 36 | bb, length, err := c.DecodePacket(b[:n]) 37 | if err != nil { 38 | return n, addr, err 39 | } 40 | copy(b, bb) 41 | return length, addr, err 42 | } 43 | -------------------------------------------------------------------------------- /adapters/outboundgroup/util.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { 12 | host, port, err := net.SplitHostPort(rawAddress) 13 | if err != nil { 14 | err = fmt.Errorf("addrToMetadata failed: %w", err) 15 | return 16 | } 17 | 18 | ip := net.ParseIP(host) 19 | if ip != nil { 20 | if ip.To4() != nil { 21 | addr = &C.Metadata{ 22 | AddrType: C.AtypIPv4, 23 | Host: "", 24 | DstIP: ip, 25 | DstPort: port, 26 | } 27 | return 28 | } else { 29 | addr = &C.Metadata{ 30 | AddrType: C.AtypIPv6, 31 | Host: "", 32 | DstIP: ip, 33 | DstPort: port, 34 | } 35 | return 36 | } 37 | } else { 38 | addr = &C.Metadata{ 39 | AddrType: C.AtypDomainName, 40 | Host: host, 41 | DstIP: nil, 42 | DstPort: port, 43 | } 44 | return 45 | } 46 | } 47 | 48 | func tcpKeepAlive(c net.Conn) { 49 | if tcp, ok := c.(*net.TCPConn); ok { 50 | tcp.SetKeepAlive(true) 51 | tcp.SetKeepAlivePeriod(30 * time.Second) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /hub/hub.go: -------------------------------------------------------------------------------- 1 | package hub 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/config" 5 | "github.com/Dreamacro/clash/hub/executor" 6 | "github.com/Dreamacro/clash/hub/route" 7 | ) 8 | 9 | type Option func(*config.Config) 10 | 11 | func WithExternalUI(externalUI string) Option { 12 | return func(cfg *config.Config) { 13 | cfg.General.ExternalUI = externalUI 14 | } 15 | } 16 | 17 | func WithExternalController(externalController string) Option { 18 | return func(cfg *config.Config) { 19 | cfg.General.ExternalController = externalController 20 | } 21 | } 22 | 23 | func WithSecret(secret string) Option { 24 | return func(cfg *config.Config) { 25 | cfg.General.Secret = secret 26 | } 27 | } 28 | 29 | // Parse call at the beginning of clash 30 | func Parse(options ...Option) error { 31 | cfg, err := executor.Parse() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | for _, option := range options { 37 | option(cfg) 38 | } 39 | 40 | if cfg.General.ExternalUI != "" { 41 | route.SetUIPath(cfg.General.ExternalUI) 42 | } 43 | 44 | if cfg.General.ExternalController != "" { 45 | go route.Start(cfg.General.ExternalController, cfg.General.Secret) 46 | } 47 | 48 | executor.ApplyConfig(cfg, true) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push, pull_request] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Setup Go 10 | uses: actions/setup-go@v2 11 | with: 12 | go-version: 1.15.x 13 | 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v2 16 | 17 | - name: Cache go module 18 | uses: actions/cache@v2 19 | with: 20 | path: ~/go/pkg/mod 21 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 22 | restore-keys: | 23 | ${{ runner.os }}-go- 24 | 25 | - name: Get dependencies and run test 26 | run: | 27 | go test ./... 28 | 29 | - name: Build 30 | if: startsWith(github.ref, 'refs/tags/') 31 | env: 32 | NAME: clash 33 | BINDIR: bin 34 | run: make -j releases 35 | 36 | - name: Upload Release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | files: bin/* 43 | draft: true 44 | prerelease: true 45 | -------------------------------------------------------------------------------- /constant/path.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "os" 5 | P "path" 6 | "path/filepath" 7 | ) 8 | 9 | const Name = "clash" 10 | 11 | // Path is used to get the configuration path 12 | var Path *path 13 | 14 | type path struct { 15 | homeDir string 16 | configFile string 17 | } 18 | 19 | func init() { 20 | homeDir, err := os.UserHomeDir() 21 | if err != nil { 22 | homeDir, _ = os.Getwd() 23 | } 24 | 25 | homeDir = P.Join(homeDir, ".config", Name) 26 | Path = &path{homeDir: homeDir, configFile: "config.yaml"} 27 | } 28 | 29 | // SetHomeDir is used to set the configuration path 30 | func SetHomeDir(root string) { 31 | Path.homeDir = root 32 | } 33 | 34 | // SetConfig is used to set the configuration file 35 | func SetConfig(file string) { 36 | Path.configFile = file 37 | } 38 | 39 | func (p *path) HomeDir() string { 40 | return p.homeDir 41 | } 42 | 43 | func (p *path) Config() string { 44 | return p.configFile 45 | } 46 | 47 | // Resolve return a absolute path or a relative path with homedir 48 | func (p *path) Resolve(path string) string { 49 | if !filepath.IsAbs(path) { 50 | return filepath.Join(p.HomeDir(), path) 51 | } 52 | 53 | return path 54 | } 55 | 56 | func (p *path) MMDB() string { 57 | return P.Join(p.homeDir, "Country.mmdb") 58 | } 59 | -------------------------------------------------------------------------------- /common/singledo/singledo.go: -------------------------------------------------------------------------------- 1 | package singledo 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type call struct { 9 | wg sync.WaitGroup 10 | val interface{} 11 | err error 12 | } 13 | 14 | type Single struct { 15 | mux sync.Mutex 16 | last time.Time 17 | wait time.Duration 18 | call *call 19 | result *Result 20 | } 21 | 22 | type Result struct { 23 | Val interface{} 24 | Err error 25 | } 26 | 27 | func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { 28 | s.mux.Lock() 29 | now := time.Now() 30 | if now.Before(s.last.Add(s.wait)) { 31 | s.mux.Unlock() 32 | return s.result.Val, s.result.Err, true 33 | } 34 | 35 | if call := s.call; call != nil { 36 | s.mux.Unlock() 37 | call.wg.Wait() 38 | return call.val, call.err, true 39 | } 40 | 41 | call := &call{} 42 | call.wg.Add(1) 43 | s.call = call 44 | s.mux.Unlock() 45 | call.val, call.err = fn() 46 | call.wg.Done() 47 | 48 | s.mux.Lock() 49 | s.call = nil 50 | s.result = &Result{call.val, call.err} 51 | s.last = now 52 | s.mux.Unlock() 53 | return call.val, call.err, false 54 | } 55 | 56 | func (s *Single) Reset() { 57 | s.last = time.Time{} 58 | } 59 | 60 | func NewSingle(wait time.Duration) *Single { 61 | return &Single{wait: wait} 62 | } 63 | -------------------------------------------------------------------------------- /proxy/redir/tcp.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapters/inbound" 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/log" 9 | "github.com/Dreamacro/clash/tunnel" 10 | ) 11 | 12 | type RedirListener struct { 13 | net.Listener 14 | address string 15 | closed bool 16 | } 17 | 18 | func NewRedirProxy(addr string) (*RedirListener, error) { 19 | l, err := net.Listen("tcp", addr) 20 | if err != nil { 21 | return nil, err 22 | } 23 | rl := &RedirListener{l, addr, false} 24 | 25 | go func() { 26 | log.Infoln("Redir proxy listening at: %s", addr) 27 | for { 28 | c, err := l.Accept() 29 | if err != nil { 30 | if rl.closed { 31 | break 32 | } 33 | continue 34 | } 35 | go handleRedir(c) 36 | } 37 | }() 38 | 39 | return rl, nil 40 | } 41 | 42 | func (l *RedirListener) Close() { 43 | l.closed = true 44 | l.Listener.Close() 45 | } 46 | 47 | func (l *RedirListener) Address() string { 48 | return l.address 49 | } 50 | 51 | func handleRedir(conn net.Conn) { 52 | target, err := parserPacket(conn) 53 | if err != nil { 54 | conn.Close() 55 | return 56 | } 57 | conn.(*net.TCPConn).SetKeepAlive(true) 58 | tunnel.Add(inbound.NewSocket(target, conn, C.REDIR)) 59 | } 60 | -------------------------------------------------------------------------------- /common/murmur3/murmur.go: -------------------------------------------------------------------------------- 1 | package murmur3 2 | 3 | type bmixer interface { 4 | bmix(p []byte) (tail []byte) 5 | Size() (n int) 6 | reset() 7 | } 8 | 9 | type digest struct { 10 | clen int // Digested input cumulative length. 11 | tail []byte // 0 to Size()-1 bytes view of `buf'. 12 | buf [16]byte // Expected (but not required) to be Size() large. 13 | seed uint32 // Seed for initializing the hash. 14 | bmixer 15 | } 16 | 17 | func (d *digest) BlockSize() int { return 1 } 18 | 19 | func (d *digest) Write(p []byte) (n int, err error) { 20 | n = len(p) 21 | d.clen += n 22 | 23 | if len(d.tail) > 0 { 24 | // Stick back pending bytes. 25 | nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. 26 | if nfree < len(p) { 27 | // One full block can be formed. 28 | block := append(d.tail, p[:nfree]...) 29 | p = p[nfree:] 30 | _ = d.bmix(block) // No tail. 31 | } else { 32 | // Tail's buf is large enough to prevent reallocs. 33 | p = append(d.tail, p...) 34 | } 35 | } 36 | 37 | d.tail = d.bmix(p) 38 | 39 | // Keep own copy of the 0 to Size()-1 pending bytes. 40 | nn := copy(d.buf[:], d.tail) 41 | d.tail = d.buf[:nn] 42 | 43 | return n, nil 44 | } 45 | 46 | func (d *digest) Reset() { 47 | d.clen = 0 48 | d.tail = nil 49 | d.bmixer.reset() 50 | } 51 | -------------------------------------------------------------------------------- /rules/parser.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { 10 | var ( 11 | parseErr error 12 | parsed C.Rule 13 | ) 14 | 15 | switch tp { 16 | case "DOMAIN": 17 | parsed = NewDomain(payload, target) 18 | case "DOMAIN-SUFFIX": 19 | parsed = NewDomainSuffix(payload, target) 20 | case "DOMAIN-KEYWORD": 21 | parsed = NewDomainKeyword(payload, target) 22 | case "GEOIP": 23 | noResolve := HasNoResolve(params) 24 | parsed = NewGEOIP(payload, target, noResolve) 25 | case "IP-CIDR", "IP-CIDR6": 26 | noResolve := HasNoResolve(params) 27 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) 28 | case "SRC-IP-CIDR": 29 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) 30 | case "SRC-PORT": 31 | parsed, parseErr = NewPort(payload, target, true) 32 | case "DST-PORT": 33 | parsed, parseErr = NewPort(payload, target, false) 34 | case "PROCESS-NAME": 35 | parsed, parseErr = NewProcess(payload, target) 36 | case "MATCH": 37 | parsed = NewMatch(target) 38 | default: 39 | parseErr = fmt.Errorf("unsupported rule type %s", tp) 40 | } 41 | 42 | return parsed, parseErr 43 | } 44 | -------------------------------------------------------------------------------- /component/v2ray-plugin/websocket.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/Dreamacro/clash/component/vmess" 9 | ) 10 | 11 | // Option is options of websocket obfs 12 | type Option struct { 13 | Host string 14 | Port string 15 | Path string 16 | Headers map[string]string 17 | TLS bool 18 | SkipCertVerify bool 19 | SessionCache tls.ClientSessionCache 20 | Mux bool 21 | } 22 | 23 | // NewV2rayObfs return a HTTPObfs 24 | func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { 25 | header := http.Header{} 26 | for k, v := range option.Headers { 27 | header.Add(k, v) 28 | } 29 | 30 | config := &vmess.WebsocketConfig{ 31 | Host: option.Host, 32 | Port: option.Port, 33 | Path: option.Path, 34 | TLS: option.TLS, 35 | Headers: header, 36 | SkipCertVerify: option.SkipCertVerify, 37 | SessionCache: option.SessionCache, 38 | } 39 | 40 | var err error 41 | conn, err = vmess.StreamWebsocketConn(conn, config) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | if option.Mux { 47 | conn = NewMux(conn, MuxOption{ 48 | ID: [2]byte{0, 0}, 49 | Host: "127.0.0.1", 50 | Port: 0, 51 | }) 52 | } 53 | return conn, nil 54 | } 55 | -------------------------------------------------------------------------------- /component/vmess/user.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | 7 | "github.com/gofrs/uuid" 8 | ) 9 | 10 | // ID cmdKey length 11 | const ( 12 | IDBytesLen = 16 13 | ) 14 | 15 | // The ID of en entity, in the form of a UUID. 16 | type ID struct { 17 | UUID *uuid.UUID 18 | CmdKey []byte 19 | } 20 | 21 | // newID returns an ID with given UUID. 22 | func newID(uuid *uuid.UUID) *ID { 23 | id := &ID{UUID: uuid, CmdKey: make([]byte, IDBytesLen)} 24 | md5hash := md5.New() 25 | md5hash.Write(uuid.Bytes()) 26 | md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) 27 | md5hash.Sum(id.CmdKey[:0]) 28 | return id 29 | } 30 | 31 | func nextID(u *uuid.UUID) *uuid.UUID { 32 | md5hash := md5.New() 33 | md5hash.Write(u.Bytes()) 34 | md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) 35 | var newid uuid.UUID 36 | for { 37 | md5hash.Sum(newid[:0]) 38 | if !bytes.Equal(newid.Bytes(), u.Bytes()) { 39 | return &newid 40 | } 41 | md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) 42 | } 43 | } 44 | 45 | func newAlterIDs(primary *ID, alterIDCount uint16) []*ID { 46 | alterIDs := make([]*ID, alterIDCount) 47 | prevID := primary.UUID 48 | for idx := range alterIDs { 49 | newid := nextID(prevID) 50 | alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]} 51 | prevID = newid 52 | } 53 | alterIDs = append(alterIDs, primary) 54 | return alterIDs 55 | } 56 | -------------------------------------------------------------------------------- /common/observable/observable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | type Observable struct { 9 | iterable Iterable 10 | listener map[Subscription]*Subscriber 11 | mux sync.Mutex 12 | done bool 13 | } 14 | 15 | func (o *Observable) process() { 16 | for item := range o.iterable { 17 | o.mux.Lock() 18 | for _, sub := range o.listener { 19 | sub.Emit(item) 20 | } 21 | o.mux.Unlock() 22 | } 23 | o.close() 24 | } 25 | 26 | func (o *Observable) close() { 27 | o.mux.Lock() 28 | defer o.mux.Unlock() 29 | 30 | o.done = true 31 | for _, sub := range o.listener { 32 | sub.Close() 33 | } 34 | } 35 | 36 | func (o *Observable) Subscribe() (Subscription, error) { 37 | o.mux.Lock() 38 | defer o.mux.Unlock() 39 | if o.done { 40 | return nil, errors.New("Observable is closed") 41 | } 42 | subscriber := newSubscriber() 43 | o.listener[subscriber.Out()] = subscriber 44 | return subscriber.Out(), nil 45 | } 46 | 47 | func (o *Observable) UnSubscribe(sub Subscription) { 48 | o.mux.Lock() 49 | defer o.mux.Unlock() 50 | subscriber, exist := o.listener[sub] 51 | if !exist { 52 | return 53 | } 54 | delete(o.listener, sub) 55 | subscriber.Close() 56 | } 57 | 58 | func NewObservable(any Iterable) *Observable { 59 | observable := &Observable{ 60 | iterable: any, 61 | listener: map[Subscription]*Subscriber{}, 62 | } 63 | go observable.process() 64 | return observable 65 | } 66 | -------------------------------------------------------------------------------- /common/singledo/singledo_test.go: -------------------------------------------------------------------------------- 1 | package singledo 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestBasic(t *testing.T) { 13 | single := NewSingle(time.Millisecond * 30) 14 | foo := 0 15 | var shardCount int32 = 0 16 | call := func() (interface{}, error) { 17 | foo++ 18 | time.Sleep(time.Millisecond * 5) 19 | return nil, nil 20 | } 21 | 22 | var wg sync.WaitGroup 23 | const n = 5 24 | wg.Add(n) 25 | for i := 0; i < n; i++ { 26 | go func() { 27 | _, _, shard := single.Do(call) 28 | if shard { 29 | atomic.AddInt32(&shardCount, 1) 30 | } 31 | wg.Done() 32 | }() 33 | } 34 | 35 | wg.Wait() 36 | assert.Equal(t, 1, foo) 37 | assert.Equal(t, int32(4), shardCount) 38 | } 39 | 40 | func TestTimer(t *testing.T) { 41 | single := NewSingle(time.Millisecond * 30) 42 | foo := 0 43 | call := func() (interface{}, error) { 44 | foo++ 45 | return nil, nil 46 | } 47 | 48 | single.Do(call) 49 | time.Sleep(10 * time.Millisecond) 50 | _, _, shard := single.Do(call) 51 | 52 | assert.Equal(t, 1, foo) 53 | assert.True(t, shard) 54 | } 55 | 56 | func TestReset(t *testing.T) { 57 | single := NewSingle(time.Millisecond * 30) 58 | foo := 0 59 | call := func() (interface{}, error) { 60 | foo++ 61 | return nil, nil 62 | } 63 | 64 | single.Do(call) 65 | single.Reset() 66 | single.Do(call) 67 | 68 | assert.Equal(t, 2, foo) 69 | } 70 | -------------------------------------------------------------------------------- /proxy/redir/tcp_linux.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/Dreamacro/clash/component/socks5" 10 | ) 11 | 12 | const ( 13 | SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h 14 | IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h 15 | ) 16 | 17 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 18 | c, ok := conn.(*net.TCPConn) 19 | if !ok { 20 | return nil, errors.New("only work with TCP connection") 21 | } 22 | 23 | rc, err := c.SyscallConn() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | var addr socks5.Addr 29 | 30 | rc.Control(func(fd uintptr) { 31 | addr, err = getorigdst(fd) 32 | }) 33 | 34 | return addr, err 35 | } 36 | 37 | // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c 38 | func getorigdst(fd uintptr) (socks5.Addr, error) { 39 | raw := syscall.RawSockaddrInet4{} 40 | siz := unsafe.Sizeof(raw) 41 | if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { 42 | return nil, err 43 | } 44 | 45 | addr := make([]byte, 1+net.IPv4len+2) 46 | addr[0] = socks5.AtypIPv4 47 | copy(addr[1:1+net.IPv4len], raw.Addr[:]) 48 | port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian 49 | addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] 50 | return addr, nil 51 | } 52 | -------------------------------------------------------------------------------- /adapters/provider/healthcheck.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | const ( 11 | defaultURLTestTimeout = time.Second * 5 12 | ) 13 | 14 | type HealthCheckOption struct { 15 | URL string 16 | Interval uint 17 | } 18 | 19 | type HealthCheck struct { 20 | url string 21 | proxies []C.Proxy 22 | interval uint 23 | done chan struct{} 24 | } 25 | 26 | func (hc *HealthCheck) process() { 27 | ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) 28 | 29 | go hc.check() 30 | for { 31 | select { 32 | case <-ticker.C: 33 | hc.check() 34 | case <-hc.done: 35 | ticker.Stop() 36 | return 37 | } 38 | } 39 | } 40 | 41 | func (hc *HealthCheck) setProxy(proxies []C.Proxy) { 42 | hc.proxies = proxies 43 | } 44 | 45 | func (hc *HealthCheck) auto() bool { 46 | return hc.interval != 0 47 | } 48 | 49 | func (hc *HealthCheck) check() { 50 | ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) 51 | for _, proxy := range hc.proxies { 52 | go proxy.URLTest(ctx, hc.url) 53 | } 54 | 55 | <-ctx.Done() 56 | cancel() 57 | } 58 | 59 | func (hc *HealthCheck) close() { 60 | hc.done <- struct{}{} 61 | } 62 | 63 | func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck { 64 | return &HealthCheck{ 65 | proxies: proxies, 66 | url: url, 67 | interval: interval, 68 | done: make(chan struct{}, 1), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /proxy/mixed/mixed.go: -------------------------------------------------------------------------------- 1 | package mixed 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/common/cache" 8 | "github.com/Dreamacro/clash/component/socks5" 9 | "github.com/Dreamacro/clash/log" 10 | 11 | "github.com/Dreamacro/clash/proxy/http" 12 | "github.com/Dreamacro/clash/proxy/socks" 13 | ) 14 | 15 | type MixedListener struct { 16 | net.Listener 17 | address string 18 | closed bool 19 | cache *cache.Cache 20 | } 21 | 22 | func NewMixedProxy(addr string) (*MixedListener, error) { 23 | l, err := net.Listen("tcp", addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)} 29 | go func() { 30 | log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr) 31 | 32 | for { 33 | c, err := ml.Accept() 34 | if err != nil { 35 | if ml.closed { 36 | break 37 | } 38 | continue 39 | } 40 | go handleConn(c, ml.cache) 41 | } 42 | }() 43 | 44 | return ml, nil 45 | } 46 | 47 | func (l *MixedListener) Close() { 48 | l.closed = true 49 | l.Listener.Close() 50 | } 51 | 52 | func (l *MixedListener) Address() string { 53 | return l.address 54 | } 55 | 56 | func handleConn(conn net.Conn, cache *cache.Cache) { 57 | bufConn := NewBufferedConn(conn) 58 | head, err := bufConn.Peek(1) 59 | if err != nil { 60 | return 61 | } 62 | 63 | if head[0] == socks5.Version { 64 | socks.HandleSocks(bufConn) 65 | return 66 | } 67 | 68 | http.HandleConn(bufConn, cache) 69 | } 70 | -------------------------------------------------------------------------------- /proxy/redir/tcp_freebsd.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/Dreamacro/clash/component/socks5" 10 | ) 11 | 12 | const ( 13 | SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h 14 | IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h 15 | ) 16 | 17 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 18 | c, ok := conn.(*net.TCPConn) 19 | if !ok { 20 | return nil, errors.New("only work with TCP connection") 21 | } 22 | 23 | rc, err := c.SyscallConn() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | var addr socks5.Addr 29 | 30 | rc.Control(func(fd uintptr) { 31 | addr, err = getorigdst(fd) 32 | }) 33 | 34 | return addr, err 35 | } 36 | 37 | // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c 38 | func getorigdst(fd uintptr) (socks5.Addr, error) { 39 | raw := syscall.RawSockaddrInet4{} 40 | siz := unsafe.Sizeof(raw) 41 | _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0) 42 | if err != 0 { 43 | return nil, err 44 | } 45 | 46 | addr := make([]byte, 1+net.IPv4len+2) 47 | addr[0] = socks5.AtypIPv4 48 | copy(addr[1:1+net.IPv4len], raw.Addr[:]) 49 | port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian 50 | addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] 51 | return addr, nil 52 | } 53 | -------------------------------------------------------------------------------- /rules/ipcidr.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type IPCIDROption func(*IPCIDR) 10 | 11 | func WithIPCIDRSourceIP(b bool) IPCIDROption { 12 | return func(i *IPCIDR) { 13 | i.isSourceIP = b 14 | } 15 | } 16 | 17 | func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { 18 | return func(i *IPCIDR) { 19 | i.noResolveIP = noResolve 20 | } 21 | } 22 | 23 | type IPCIDR struct { 24 | ipnet *net.IPNet 25 | adapter string 26 | isSourceIP bool 27 | noResolveIP bool 28 | } 29 | 30 | func (i *IPCIDR) RuleType() C.RuleType { 31 | if i.isSourceIP { 32 | return C.SrcIPCIDR 33 | } 34 | return C.IPCIDR 35 | } 36 | 37 | func (i *IPCIDR) Match(metadata *C.Metadata) bool { 38 | ip := metadata.DstIP 39 | if i.isSourceIP { 40 | ip = metadata.SrcIP 41 | } 42 | return ip != nil && i.ipnet.Contains(ip) 43 | } 44 | 45 | func (i *IPCIDR) Adapter() string { 46 | return i.adapter 47 | } 48 | 49 | func (i *IPCIDR) Payload() string { 50 | return i.ipnet.String() 51 | } 52 | 53 | func (i *IPCIDR) ShouldResolveIP() bool { 54 | return !i.noResolveIP 55 | } 56 | 57 | func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { 58 | _, ipnet, err := net.ParseCIDR(s) 59 | if err != nil { 60 | return nil, errPayload 61 | } 62 | 63 | ipcidr := &IPCIDR{ 64 | ipnet: ipnet, 65 | adapter: adapter, 66 | } 67 | 68 | for _, o := range opts { 69 | o(ipcidr) 70 | } 71 | 72 | return ipcidr, nil 73 | } 74 | -------------------------------------------------------------------------------- /common/queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Queue is a simple concurrent safe queue 8 | type Queue struct { 9 | items []interface{} 10 | lock sync.RWMutex 11 | } 12 | 13 | // Put add the item to the queue. 14 | func (q *Queue) Put(items ...interface{}) { 15 | if len(items) == 0 { 16 | return 17 | } 18 | 19 | q.lock.Lock() 20 | q.items = append(q.items, items...) 21 | q.lock.Unlock() 22 | } 23 | 24 | // Pop returns the head of items. 25 | func (q *Queue) Pop() interface{} { 26 | if len(q.items) == 0 { 27 | return nil 28 | } 29 | 30 | q.lock.Lock() 31 | head := q.items[0] 32 | q.items = q.items[1:] 33 | q.lock.Unlock() 34 | return head 35 | } 36 | 37 | // Last returns the last of item. 38 | func (q *Queue) Last() interface{} { 39 | if len(q.items) == 0 { 40 | return nil 41 | } 42 | 43 | q.lock.RLock() 44 | last := q.items[len(q.items)-1] 45 | q.lock.RUnlock() 46 | return last 47 | } 48 | 49 | // Copy get the copy of queue. 50 | func (q *Queue) Copy() []interface{} { 51 | items := []interface{}{} 52 | q.lock.RLock() 53 | items = append(items, q.items...) 54 | q.lock.RUnlock() 55 | return items 56 | } 57 | 58 | // Len returns the number of items in this queue. 59 | func (q *Queue) Len() int64 { 60 | q.lock.Lock() 61 | defer q.lock.Unlock() 62 | 63 | return int64(len(q.items)) 64 | } 65 | 66 | // New is a constructor for a new concurrent safe queue. 67 | func New(hint int64) *Queue { 68 | return &Queue{ 69 | items: make([]interface{}, 0, hint), 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tunnel/mode.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | type TunnelMode int 10 | 11 | var ( 12 | // ModeMapping is a mapping for Mode enum 13 | ModeMapping = map[string]TunnelMode{ 14 | Global.String(): Global, 15 | Rule.String(): Rule, 16 | Direct.String(): Direct, 17 | } 18 | ) 19 | 20 | const ( 21 | Global TunnelMode = iota 22 | Rule 23 | Direct 24 | ) 25 | 26 | // UnmarshalJSON unserialize Mode 27 | func (m *TunnelMode) UnmarshalJSON(data []byte) error { 28 | var tp string 29 | json.Unmarshal(data, &tp) 30 | mode, exist := ModeMapping[strings.ToLower(tp)] 31 | if !exist { 32 | return errors.New("invalid mode") 33 | } 34 | *m = mode 35 | return nil 36 | } 37 | 38 | // UnmarshalYAML unserialize Mode with yaml 39 | func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error { 40 | var tp string 41 | unmarshal(&tp) 42 | mode, exist := ModeMapping[strings.ToLower(tp)] 43 | if !exist { 44 | return errors.New("invalid mode") 45 | } 46 | *m = mode 47 | return nil 48 | } 49 | 50 | // MarshalJSON serialize Mode 51 | func (m TunnelMode) MarshalJSON() ([]byte, error) { 52 | return json.Marshal(m.String()) 53 | } 54 | 55 | // MarshalYAML serialize TunnelMode with yaml 56 | func (m TunnelMode) MarshalYAML() (interface{}, error) { 57 | return m.String(), nil 58 | } 59 | 60 | func (m TunnelMode) String() string { 61 | switch m { 62 | case Global: 63 | return "global" 64 | case Rule: 65 | return "rule" 66 | case Direct: 67 | return "direct" 68 | default: 69 | return "Unknown" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /adapters/outbound/reject.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "net" 8 | "time" 9 | 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | type Reject struct { 14 | *Base 15 | } 16 | 17 | func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 18 | return NewConn(&NopConn{}, r), nil 19 | } 20 | 21 | func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 22 | return nil, errors.New("match reject rule") 23 | } 24 | 25 | func NewReject() *Reject { 26 | return &Reject{ 27 | Base: &Base{ 28 | name: "REJECT", 29 | tp: C.Reject, 30 | udp: true, 31 | }, 32 | } 33 | } 34 | 35 | type NopConn struct{} 36 | 37 | func (rw *NopConn) Read(b []byte) (int, error) { 38 | return 0, io.EOF 39 | } 40 | 41 | func (rw *NopConn) Write(b []byte) (int, error) { 42 | return 0, io.EOF 43 | } 44 | 45 | // Close is fake function for net.Conn 46 | func (rw *NopConn) Close() error { return nil } 47 | 48 | // LocalAddr is fake function for net.Conn 49 | func (rw *NopConn) LocalAddr() net.Addr { return nil } 50 | 51 | // RemoteAddr is fake function for net.Conn 52 | func (rw *NopConn) RemoteAddr() net.Addr { return nil } 53 | 54 | // SetDeadline is fake function for net.Conn 55 | func (rw *NopConn) SetDeadline(time.Time) error { return nil } 56 | 57 | // SetReadDeadline is fake function for net.Conn 58 | func (rw *NopConn) SetReadDeadline(time.Time) error { return nil } 59 | 60 | // SetWriteDeadline is fake function for net.Conn 61 | func (rw *NopConn) SetWriteDeadline(time.Time) error { return nil } 62 | -------------------------------------------------------------------------------- /component/ssr/protocol/stream.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/common/pool" 8 | ) 9 | 10 | // NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding 11 | func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn { 12 | return &Conn{Conn: c, Protocol: p.initForConn(iv)} 13 | } 14 | 15 | // Conn represents a protocol connection 16 | type Conn struct { 17 | net.Conn 18 | Protocol 19 | buf []byte 20 | offset int 21 | underDecoded bytes.Buffer 22 | } 23 | 24 | func (c *Conn) Read(b []byte) (int, error) { 25 | if c.buf != nil { 26 | n := copy(b, c.buf[c.offset:]) 27 | c.offset += n 28 | if c.offset == len(c.buf) { 29 | c.buf = nil 30 | } 31 | return n, nil 32 | } 33 | buf := pool.Get(pool.RelayBufferSize) 34 | defer pool.Put(buf) 35 | n, err := c.Conn.Read(buf) 36 | if err != nil { 37 | return 0, err 38 | } 39 | c.underDecoded.Write(buf[:n]) 40 | underDecoded := c.underDecoded.Bytes() 41 | decoded, length, err := c.Decode(underDecoded) 42 | if err != nil { 43 | c.underDecoded.Reset() 44 | return 0, nil 45 | } 46 | if length == 0 { 47 | return 0, nil 48 | } 49 | c.underDecoded.Next(length) 50 | n = copy(b, decoded) 51 | if len(decoded) > len(b) { 52 | c.buf = decoded 53 | c.offset = n 54 | } 55 | return n, nil 56 | } 57 | 58 | func (c *Conn) Write(b []byte) (int, error) { 59 | encoded, err := c.Encode(b) 60 | if err != nil { 61 | return 0, err 62 | } 63 | _, err = c.Conn.Write(encoded) 64 | if err != nil { 65 | return 0, err 66 | } 67 | return len(b), nil 68 | } 69 | -------------------------------------------------------------------------------- /proxy/socks/tcp.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "net" 7 | 8 | adapters "github.com/Dreamacro/clash/adapters/inbound" 9 | "github.com/Dreamacro/clash/component/socks5" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | authStore "github.com/Dreamacro/clash/proxy/auth" 13 | "github.com/Dreamacro/clash/tunnel" 14 | ) 15 | 16 | type SockListener struct { 17 | net.Listener 18 | address string 19 | closed bool 20 | } 21 | 22 | func NewSocksProxy(addr string) (*SockListener, error) { 23 | l, err := net.Listen("tcp", addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | sl := &SockListener{l, addr, false} 29 | go func() { 30 | log.Infoln("SOCKS proxy listening at: %s", addr) 31 | for { 32 | c, err := l.Accept() 33 | if err != nil { 34 | if sl.closed { 35 | break 36 | } 37 | continue 38 | } 39 | go HandleSocks(c) 40 | } 41 | }() 42 | 43 | return sl, nil 44 | } 45 | 46 | func (l *SockListener) Close() { 47 | l.closed = true 48 | l.Listener.Close() 49 | } 50 | 51 | func (l *SockListener) Address() string { 52 | return l.address 53 | } 54 | 55 | func HandleSocks(conn net.Conn) { 56 | target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) 57 | if err != nil { 58 | conn.Close() 59 | return 60 | } 61 | if c, ok := conn.(*net.TCPConn); ok { 62 | c.SetKeepAlive(true) 63 | } 64 | if command == socks5.CmdUDPAssociate { 65 | defer conn.Close() 66 | io.Copy(ioutil.Discard, conn) 67 | return 68 | } 69 | tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS)) 70 | } 71 | -------------------------------------------------------------------------------- /component/ssr/obfs/stream.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | // NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding 10 | func NewConn(c net.Conn, o Obfs) net.Conn { 11 | return &Conn{Conn: c, Obfs: o.initForConn()} 12 | } 13 | 14 | // Conn represents an obfs connection 15 | type Conn struct { 16 | net.Conn 17 | Obfs 18 | buf []byte 19 | offset int 20 | } 21 | 22 | func (c *Conn) Read(b []byte) (int, error) { 23 | if c.buf != nil { 24 | n := copy(b, c.buf[c.offset:]) 25 | c.offset += n 26 | if c.offset == len(c.buf) { 27 | pool.Put(c.buf) 28 | c.buf = nil 29 | } 30 | return n, nil 31 | } 32 | 33 | buf := pool.Get(pool.RelayBufferSize) 34 | defer pool.Put(buf) 35 | n, err := c.Conn.Read(buf) 36 | if err != nil { 37 | return 0, err 38 | } 39 | decoded, sendback, err := c.Decode(buf[:n]) 40 | // decoded may be part of buf 41 | decodedData := pool.Get(len(decoded)) 42 | copy(decodedData, decoded) 43 | if err != nil { 44 | pool.Put(decodedData) 45 | return 0, err 46 | } 47 | if sendback { 48 | c.Write(nil) 49 | pool.Put(decodedData) 50 | return 0, nil 51 | } 52 | n = copy(b, decodedData) 53 | if len(decodedData) > len(b) { 54 | c.buf = decodedData 55 | c.offset = n 56 | } else { 57 | pool.Put(decodedData) 58 | } 59 | return n, err 60 | } 61 | 62 | func (c *Conn) Write(b []byte) (int, error) { 63 | encoded, err := c.Encode(b) 64 | if err != nil { 65 | return 0, err 66 | } 67 | _, err = c.Conn.Write(encoded) 68 | if err != nil { 69 | return 0, err 70 | } 71 | return len(b), nil 72 | } 73 | -------------------------------------------------------------------------------- /common/pool/alloc_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAllocGet(t *testing.T) { 11 | alloc := NewAllocator() 12 | assert.Nil(t, alloc.Get(0)) 13 | assert.Equal(t, 1, len(alloc.Get(1))) 14 | assert.Equal(t, 2, len(alloc.Get(2))) 15 | assert.Equal(t, 3, len(alloc.Get(3))) 16 | assert.Equal(t, 4, cap(alloc.Get(3))) 17 | assert.Equal(t, 4, cap(alloc.Get(4))) 18 | assert.Equal(t, 1023, len(alloc.Get(1023))) 19 | assert.Equal(t, 1024, cap(alloc.Get(1023))) 20 | assert.Equal(t, 1024, len(alloc.Get(1024))) 21 | assert.Equal(t, 65536, len(alloc.Get(65536))) 22 | assert.Nil(t, alloc.Get(65537)) 23 | } 24 | 25 | func TestAllocPut(t *testing.T) { 26 | alloc := NewAllocator() 27 | assert.NotNil(t, alloc.Put(nil), "put nil misbehavior") 28 | assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior") 29 | assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior") 30 | assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior") 31 | assert.Nil(t, alloc.Put(make([]byte, 65536, 65536)), "put elem:65536 []bytes misbehavior") 32 | assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior") 33 | } 34 | 35 | func TestAllocPutThenGet(t *testing.T) { 36 | alloc := NewAllocator() 37 | data := alloc.Get(4) 38 | alloc.Put(data) 39 | newData := alloc.Get(4) 40 | 41 | assert.Equal(t, cap(data), cap(newData), "different cap while alloc.Get()") 42 | } 43 | 44 | func BenchmarkMSB(b *testing.B) { 45 | for i := 0; i < b.N; i++ { 46 | msb(rand.Int()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dns/client.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | "github.com/Dreamacro/clash/component/dialer" 10 | 11 | D "github.com/miekg/dns" 12 | ) 13 | 14 | type client struct { 15 | *D.Client 16 | r *Resolver 17 | port string 18 | host string 19 | } 20 | 21 | func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { 22 | return c.ExchangeContext(context.Background(), m) 23 | } 24 | 25 | func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 26 | var ip net.IP 27 | if c.r == nil { 28 | // a default ip dns 29 | ip = net.ParseIP(c.host) 30 | } else { 31 | var err error 32 | if ip, err = c.r.ResolveIP(c.host); err != nil { 33 | return nil, fmt.Errorf("use default dns resolve failed: %w", err) 34 | } 35 | } 36 | 37 | d, err := dialer.Dialer() 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if dialer.DialHook != nil { 43 | network := "udp" 44 | if strings.HasPrefix(c.Client.Net, "tcp") { 45 | network = "tcp" 46 | } 47 | if err := dialer.DialHook(d, network, ip); err != nil { 48 | return nil, err 49 | } 50 | } 51 | 52 | c.Client.Dialer = d 53 | 54 | // miekg/dns ExchangeContext doesn't respond to context cancel. 55 | // this is a workaround 56 | type result struct { 57 | msg *D.Msg 58 | err error 59 | } 60 | ch := make(chan result, 1) 61 | go func() { 62 | msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) 63 | ch <- result{msg, err} 64 | }() 65 | 66 | select { 67 | case <-ctx.Done(): 68 | return nil, ctx.Err() 69 | case ret := <-ch: 70 | return ret.msg, ret.err 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /adapters/inbound/http.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | // HTTPAdapter is a adapter for HTTP connection 12 | type HTTPAdapter struct { 13 | net.Conn 14 | metadata *C.Metadata 15 | R *http.Request 16 | } 17 | 18 | // Metadata return destination metadata 19 | func (h *HTTPAdapter) Metadata() *C.Metadata { 20 | return h.metadata 21 | } 22 | 23 | // NewHTTP is HTTPAdapter generator 24 | func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { 25 | metadata := parseHTTPAddr(request) 26 | metadata.Type = C.HTTP 27 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 28 | metadata.SrcIP = ip 29 | metadata.SrcPort = port 30 | } 31 | return &HTTPAdapter{ 32 | metadata: metadata, 33 | R: request, 34 | Conn: conn, 35 | } 36 | } 37 | 38 | // RemoveHopByHopHeaders remove hop-by-hop header 39 | func RemoveHopByHopHeaders(header http.Header) { 40 | // Strip hop-by-hop header based on RFC: 41 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 42 | // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do 43 | 44 | header.Del("Proxy-Connection") 45 | header.Del("Proxy-Authenticate") 46 | header.Del("Proxy-Authorization") 47 | header.Del("TE") 48 | header.Del("Trailers") 49 | header.Del("Transfer-Encoding") 50 | header.Del("Upgrade") 51 | 52 | connections := header.Get("Connection") 53 | header.Del("Connection") 54 | if len(connections) == 0 { 55 | return 56 | } 57 | for _, h := range strings.Split(connections, ",") { 58 | header.Del(strings.TrimSpace(h)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /dns/server.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/sockopt" 7 | "github.com/Dreamacro/clash/log" 8 | 9 | D "github.com/miekg/dns" 10 | ) 11 | 12 | var ( 13 | address string 14 | server = &Server{} 15 | 16 | dnsDefaultTTL uint32 = 600 17 | ) 18 | 19 | type Server struct { 20 | *D.Server 21 | handler handler 22 | } 23 | 24 | func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { 25 | if len(r.Question) == 0 { 26 | D.HandleFailed(w, r) 27 | return 28 | } 29 | 30 | s.handler(w, r) 31 | } 32 | 33 | func (s *Server) setHandler(handler handler) { 34 | s.handler = handler 35 | } 36 | 37 | func ReCreateServer(addr string, resolver *Resolver) error { 38 | if addr == address && resolver != nil { 39 | handler := newHandler(resolver) 40 | server.setHandler(handler) 41 | return nil 42 | } 43 | 44 | if server.Server != nil { 45 | server.Shutdown() 46 | server = &Server{} 47 | address = "" 48 | } 49 | 50 | _, port, err := net.SplitHostPort(addr) 51 | if port == "0" || port == "" || err != nil { 52 | return nil 53 | } 54 | 55 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | p, err := net.ListenUDP("udp", udpAddr) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | err = sockopt.UDPReuseaddr(p) 66 | if err != nil { 67 | log.Warnln("Failed to Reuse UDP Address: %s", err) 68 | } 69 | 70 | address = addr 71 | handler := newHandler(resolver) 72 | server = &Server{handler: handler} 73 | server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} 74 | 75 | go func() { 76 | server.ActivateAndServe() 77 | }() 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /component/ssr/obfs/random_head.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "encoding/binary" 5 | "hash/crc32" 6 | "math/rand" 7 | ) 8 | 9 | type randomHead struct { 10 | *Base 11 | firstRequest bool 12 | firstResponse bool 13 | headerSent bool 14 | buffer []byte 15 | } 16 | 17 | func init() { 18 | register("random_head", newRandomHead) 19 | } 20 | 21 | func newRandomHead(b *Base) Obfs { 22 | return &randomHead{Base: b} 23 | } 24 | 25 | func (r *randomHead) initForConn() Obfs { 26 | return &randomHead{ 27 | Base: r.Base, 28 | firstRequest: true, 29 | firstResponse: true, 30 | } 31 | } 32 | 33 | func (r *randomHead) GetObfsOverhead() int { 34 | return 0 35 | } 36 | 37 | func (r *randomHead) Encode(b []byte) (encoded []byte, err error) { 38 | if !r.firstRequest { 39 | return b, nil 40 | } 41 | 42 | bSize := len(b) 43 | if r.headerSent { 44 | if bSize > 0 { 45 | d := make([]byte, len(r.buffer)+bSize) 46 | copy(d, r.buffer) 47 | copy(d[len(r.buffer):], b) 48 | r.buffer = d 49 | } else { 50 | encoded = r.buffer 51 | r.buffer = nil 52 | r.firstRequest = false 53 | } 54 | } else { 55 | size := rand.Intn(96) + 8 56 | encoded = make([]byte, size) 57 | rand.Read(encoded) 58 | crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF 59 | binary.LittleEndian.PutUint32(encoded[size-4:], crc) 60 | 61 | d := make([]byte, bSize) 62 | copy(d, b) 63 | r.buffer = d 64 | } 65 | r.headerSent = true 66 | return encoded, nil 67 | } 68 | 69 | func (r *randomHead) Decode(b []byte) ([]byte, bool, error) { 70 | if r.firstResponse { 71 | r.firstResponse = false 72 | return b, true, nil 73 | } 74 | return b, false, nil 75 | } 76 | -------------------------------------------------------------------------------- /proxy/redir/udp.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | adapters "github.com/Dreamacro/clash/adapters/inbound" 7 | "github.com/Dreamacro/clash/common/pool" 8 | "github.com/Dreamacro/clash/component/socks5" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/tunnel" 11 | ) 12 | 13 | type RedirUDPListener struct { 14 | net.PacketConn 15 | address string 16 | closed bool 17 | } 18 | 19 | func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { 20 | l, err := net.ListenPacket("udp", addr) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | rl := &RedirUDPListener{l, addr, false} 26 | 27 | c := l.(*net.UDPConn) 28 | 29 | err = setsockopt(c, addr) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | go func() { 35 | oob := make([]byte, 1024) 36 | for { 37 | buf := pool.Get(pool.RelayBufferSize) 38 | n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) 39 | if err != nil { 40 | pool.Put(buf) 41 | if rl.closed { 42 | break 43 | } 44 | continue 45 | } 46 | 47 | rAddr, err := getOrigDst(oob, oobn) 48 | if err != nil { 49 | continue 50 | } 51 | handleRedirUDP(l, buf[:n], lAddr, rAddr) 52 | } 53 | }() 54 | 55 | return rl, nil 56 | } 57 | 58 | func (l *RedirUDPListener) Close() error { 59 | l.closed = true 60 | return l.PacketConn.Close() 61 | } 62 | 63 | func (l *RedirUDPListener) Address() string { 64 | return l.address 65 | } 66 | 67 | func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { 68 | target := socks5.ParseAddrToSocksAddr(rAddr) 69 | pkt := &packet{ 70 | lAddr: lAddr, 71 | buf: buf, 72 | } 73 | tunnel.AddPacket(adapters.NewPacket(target, pkt, C.REDIR)) 74 | } 75 | -------------------------------------------------------------------------------- /log/level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // LogLevelMapping is a mapping for LogLevel enum 10 | LogLevelMapping = map[string]LogLevel{ 11 | ERROR.String(): ERROR, 12 | WARNING.String(): WARNING, 13 | INFO.String(): INFO, 14 | DEBUG.String(): DEBUG, 15 | SILENT.String(): SILENT, 16 | } 17 | ) 18 | 19 | const ( 20 | DEBUG LogLevel = iota 21 | INFO 22 | WARNING 23 | ERROR 24 | SILENT 25 | ) 26 | 27 | type LogLevel int 28 | 29 | // UnmarshalYAML unserialize LogLevel with yaml 30 | func (l *LogLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 31 | var tp string 32 | unmarshal(&tp) 33 | level, exist := LogLevelMapping[tp] 34 | if !exist { 35 | return errors.New("invalid mode") 36 | } 37 | *l = level 38 | return nil 39 | } 40 | 41 | // UnmarshalJSON unserialize LogLevel with json 42 | func (l *LogLevel) UnmarshalJSON(data []byte) error { 43 | var tp string 44 | json.Unmarshal(data, &tp) 45 | level, exist := LogLevelMapping[tp] 46 | if !exist { 47 | return errors.New("invalid mode") 48 | } 49 | *l = level 50 | return nil 51 | } 52 | 53 | // MarshalJSON serialize LogLevel with json 54 | func (l LogLevel) MarshalJSON() ([]byte, error) { 55 | return json.Marshal(l.String()) 56 | } 57 | 58 | // MarshalYAML serialize LogLevel with yaml 59 | func (l LogLevel) MarshalYAML() (interface{}, error) { 60 | return l.String(), nil 61 | } 62 | 63 | func (l LogLevel) String() string { 64 | switch l { 65 | case INFO: 66 | return "info" 67 | case WARNING: 68 | return "warning" 69 | case ERROR: 70 | return "error" 71 | case DEBUG: 72 | return "debug" 73 | case SILENT: 74 | return "silent" 75 | default: 76 | return "unknown" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /common/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCache_Basic(t *testing.T) { 12 | interval := 200 * time.Millisecond 13 | ttl := 20 * time.Millisecond 14 | c := New(interval) 15 | c.Put("int", 1, ttl) 16 | c.Put("string", "a", ttl) 17 | 18 | i := c.Get("int") 19 | assert.Equal(t, i.(int), 1, "should recv 1") 20 | 21 | s := c.Get("string") 22 | assert.Equal(t, s.(string), "a", "should recv 'a'") 23 | } 24 | 25 | func TestCache_TTL(t *testing.T) { 26 | interval := 200 * time.Millisecond 27 | ttl := 20 * time.Millisecond 28 | now := time.Now() 29 | c := New(interval) 30 | c.Put("int", 1, ttl) 31 | c.Put("int2", 2, ttl) 32 | 33 | i := c.Get("int") 34 | _, expired := c.GetWithExpire("int2") 35 | assert.Equal(t, i.(int), 1, "should recv 1") 36 | assert.True(t, now.Before(expired)) 37 | 38 | time.Sleep(ttl * 2) 39 | i = c.Get("int") 40 | j, _ := c.GetWithExpire("int2") 41 | assert.Nil(t, i, "should recv nil") 42 | assert.Nil(t, j, "should recv nil") 43 | } 44 | 45 | func TestCache_AutoCleanup(t *testing.T) { 46 | interval := 10 * time.Millisecond 47 | ttl := 15 * time.Millisecond 48 | c := New(interval) 49 | c.Put("int", 1, ttl) 50 | 51 | time.Sleep(ttl * 2) 52 | i := c.Get("int") 53 | j, _ := c.GetWithExpire("int") 54 | assert.Nil(t, i, "should recv nil") 55 | assert.Nil(t, j, "should recv nil") 56 | } 57 | 58 | func TestCache_AutoGC(t *testing.T) { 59 | sign := make(chan struct{}) 60 | go func() { 61 | interval := 10 * time.Millisecond 62 | ttl := 15 * time.Millisecond 63 | c := New(interval) 64 | c.Put("int", 1, ttl) 65 | sign <- struct{}{} 66 | }() 67 | 68 | <-sign 69 | runtime.GC() 70 | } 71 | -------------------------------------------------------------------------------- /component/vmess/http.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "net/http" 9 | "net/textproto" 10 | ) 11 | 12 | type httpConn struct { 13 | net.Conn 14 | cfg *HTTPConfig 15 | rhandshake bool 16 | whandshake bool 17 | } 18 | 19 | type HTTPConfig struct { 20 | Method string 21 | Host string 22 | Path []string 23 | Headers map[string][]string 24 | } 25 | 26 | // Read implements net.Conn.Read() 27 | func (hc *httpConn) Read(b []byte) (int, error) { 28 | if hc.rhandshake { 29 | n, err := hc.Conn.Read(b) 30 | return n, err 31 | } 32 | 33 | reader := textproto.NewConn(hc.Conn) 34 | // First line: GET /index.html HTTP/1.0 35 | if _, err := reader.ReadLine(); err != nil { 36 | return 0, err 37 | } 38 | 39 | if _, err := reader.ReadMIMEHeader(); err != nil { 40 | return 0, err 41 | } 42 | 43 | hc.rhandshake = true 44 | return hc.Conn.Read(b) 45 | } 46 | 47 | // Write implements io.Writer. 48 | func (hc *httpConn) Write(b []byte) (int, error) { 49 | if hc.whandshake { 50 | return hc.Conn.Write(b) 51 | } 52 | 53 | path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] 54 | u := fmt.Sprintf("http://%s%s", hc.cfg.Host, path) 55 | req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b)) 56 | for key, list := range hc.cfg.Headers { 57 | req.Header.Set(key, list[rand.Intn(len(list))]) 58 | } 59 | req.ContentLength = int64(len(b)) 60 | if err := req.Write(hc.Conn); err != nil { 61 | return 0, err 62 | } 63 | hc.whandshake = true 64 | return len(b), nil 65 | } 66 | 67 | func (hc *httpConn) Close() error { 68 | return hc.Conn.Close() 69 | } 70 | 71 | func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn { 72 | return &httpConn{ 73 | Conn: conn, 74 | cfg: cfg, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /common/pool/alloc.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | // Inspired by https://github.com/xtaci/smux/blob/master/alloc.go 4 | 5 | import ( 6 | "errors" 7 | "math/bits" 8 | "sync" 9 | ) 10 | 11 | var defaultAllocator *Allocator 12 | 13 | func init() { 14 | defaultAllocator = NewAllocator() 15 | } 16 | 17 | // Allocator for incoming frames, optimized to prevent overwriting after zeroing 18 | type Allocator struct { 19 | buffers []sync.Pool 20 | } 21 | 22 | // NewAllocator initiates a []byte allocator for frames less than 65536 bytes, 23 | // the waste(memory fragmentation) of space allocation is guaranteed to be 24 | // no more than 50%. 25 | func NewAllocator() *Allocator { 26 | alloc := new(Allocator) 27 | alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K 28 | for k := range alloc.buffers { 29 | i := k 30 | alloc.buffers[k].New = func() interface{} { 31 | return make([]byte, 1< 65536 { 40 | return nil 41 | } 42 | 43 | bits := msb(size) 44 | if size == 1< 65536 || cap(buf) != 1<>8), byte(saddr.Port) 47 | nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) 48 | 49 | if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { 50 | return nil, errno 51 | } 52 | 53 | addr := make([]byte, 1+net.IPv4len+2) 54 | addr[0] = socks5.AtypIPv4 55 | copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) 56 | copy(addr[1+net.IPv4len:], nl.rdxport[:2]) 57 | return addr, nil 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | on: 3 | push: 4 | branches: 5 | - dev 6 | tags: 7 | - '*' 8 | jobs: 9 | 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up docker buildx 21 | id: buildx 22 | uses: crazy-max/ghaction-docker-buildx@v2 23 | with: 24 | buildx-version: latest 25 | skip-cache: false 26 | qemu-version: latest 27 | 28 | - name: Docker login 29 | env: 30 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 31 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 32 | run: | 33 | echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin 34 | 35 | - name: Docker buildx image and push on dev branch 36 | if: github.ref == 'refs/heads/dev' 37 | run: | 38 | docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev . 39 | 40 | - name: Replace tag without `v` 41 | if: startsWith(github.ref, 'refs/tags/') 42 | uses: actions/github-script@v1 43 | id: version 44 | with: 45 | script: | 46 | return context.payload.ref.replace(/\/?refs\/tags\/v/, '') 47 | result-encoding: string 48 | 49 | - name: Docker buildx image and push on release 50 | if: startsWith(github.ref, 'refs/tags/') 51 | run: | 52 | docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} . 53 | docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest . 54 | -------------------------------------------------------------------------------- /adapters/inbound/util.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/Dreamacro/clash/component/socks5" 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | func parseSocksAddr(target socks5.Addr) *C.Metadata { 14 | metadata := &C.Metadata{ 15 | AddrType: int(target[0]), 16 | } 17 | 18 | switch target[0] { 19 | case socks5.AtypDomainName: 20 | // trim for FQDN 21 | metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") 22 | metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) 23 | case socks5.AtypIPv4: 24 | ip := net.IP(target[1 : 1+net.IPv4len]) 25 | metadata.DstIP = ip 26 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) 27 | case socks5.AtypIPv6: 28 | ip := net.IP(target[1 : 1+net.IPv6len]) 29 | metadata.DstIP = ip 30 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) 31 | } 32 | 33 | return metadata 34 | } 35 | 36 | func parseHTTPAddr(request *http.Request) *C.Metadata { 37 | host := request.URL.Hostname() 38 | port := request.URL.Port() 39 | if port == "" { 40 | port = "80" 41 | } 42 | 43 | // trim FQDN (#737) 44 | host = strings.TrimRight(host, ".") 45 | 46 | metadata := &C.Metadata{ 47 | NetWork: C.TCP, 48 | AddrType: C.AtypDomainName, 49 | Host: host, 50 | DstIP: nil, 51 | DstPort: port, 52 | } 53 | 54 | ip := net.ParseIP(host) 55 | if ip != nil { 56 | switch { 57 | case ip.To4() == nil: 58 | metadata.AddrType = C.AtypIPv6 59 | default: 60 | metadata.AddrType = C.AtypIPv4 61 | } 62 | metadata.DstIP = ip 63 | } 64 | 65 | return metadata 66 | } 67 | 68 | func parseAddr(addr string) (net.IP, string, error) { 69 | host, port, err := net.SplitHostPort(addr) 70 | if err != nil { 71 | return nil, "", err 72 | } 73 | 74 | ip := net.ParseIP(host) 75 | return ip, port, nil 76 | } 77 | -------------------------------------------------------------------------------- /component/fakeip/pool_test.go: -------------------------------------------------------------------------------- 1 | package fakeip 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPool_Basic(t *testing.T) { 11 | _, ipnet, _ := net.ParseCIDR("192.168.0.1/29") 12 | pool, _ := New(ipnet, 10, nil) 13 | 14 | first := pool.Lookup("foo.com") 15 | last := pool.Lookup("bar.com") 16 | bar, exist := pool.LookBack(last) 17 | 18 | assert.True(t, first.Equal(net.IP{192, 168, 0, 2})) 19 | assert.True(t, last.Equal(net.IP{192, 168, 0, 3})) 20 | assert.True(t, exist) 21 | assert.Equal(t, bar, "bar.com") 22 | } 23 | 24 | func TestPool_Cycle(t *testing.T) { 25 | _, ipnet, _ := net.ParseCIDR("192.168.0.1/30") 26 | pool, _ := New(ipnet, 10, nil) 27 | 28 | first := pool.Lookup("foo.com") 29 | same := pool.Lookup("baz.com") 30 | 31 | assert.True(t, first.Equal(same)) 32 | } 33 | 34 | func TestPool_MaxCacheSize(t *testing.T) { 35 | _, ipnet, _ := net.ParseCIDR("192.168.0.1/24") 36 | pool, _ := New(ipnet, 2, nil) 37 | 38 | first := pool.Lookup("foo.com") 39 | pool.Lookup("bar.com") 40 | pool.Lookup("baz.com") 41 | next := pool.Lookup("foo.com") 42 | 43 | assert.False(t, first.Equal(next)) 44 | } 45 | 46 | func TestPool_DoubleMapping(t *testing.T) { 47 | _, ipnet, _ := net.ParseCIDR("192.168.0.1/24") 48 | pool, _ := New(ipnet, 2, nil) 49 | 50 | // fill cache 51 | fooIP := pool.Lookup("foo.com") 52 | bazIP := pool.Lookup("baz.com") 53 | 54 | // make foo.com hot 55 | pool.Lookup("foo.com") 56 | 57 | // should drop baz.com 58 | barIP := pool.Lookup("bar.com") 59 | 60 | _, fooExist := pool.LookBack(fooIP) 61 | _, bazExist := pool.LookBack(bazIP) 62 | _, barExist := pool.LookBack(barIP) 63 | 64 | newBazIP := pool.Lookup("baz.com") 65 | 66 | assert.True(t, fooExist) 67 | assert.False(t, bazExist) 68 | assert.True(t, barExist) 69 | 70 | assert.False(t, bazIP.Equal(newBazIP)) 71 | } 72 | 73 | func TestPool_Error(t *testing.T) { 74 | _, ipnet, _ := net.ParseCIDR("192.168.0.1/31") 75 | _, err := New(ipnet, 10, nil) 76 | 77 | assert.Error(t, err) 78 | } 79 | -------------------------------------------------------------------------------- /proxy/redir/udp_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package redir 4 | 5 | import ( 6 | "encoding/binary" 7 | "errors" 8 | "net" 9 | "syscall" 10 | ) 11 | 12 | const ( 13 | IPV6_TRANSPARENT = 0x4b 14 | IPV6_RECVORIGDSTADDR = 0x4a 15 | ) 16 | 17 | func setsockopt(c *net.UDPConn, addr string) error { 18 | isIPv6 := true 19 | host, _, err := net.SplitHostPort(addr) 20 | if err != nil { 21 | return err 22 | } 23 | ip := net.ParseIP(host) 24 | if ip != nil && ip.To4() != nil { 25 | isIPv6 = false 26 | } 27 | 28 | rc, err := c.SyscallConn() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | rc.Control(func(fd uintptr) { 34 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 35 | 36 | if err == nil { 37 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) 38 | } 39 | if err == nil && isIPv6 { 40 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) 41 | } 42 | 43 | if err == nil { 44 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) 45 | } 46 | if err == nil && isIPv6 { 47 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) 48 | } 49 | }) 50 | 51 | return err 52 | } 53 | 54 | func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { 55 | msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | for _, msg := range msgs { 61 | if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { 62 | ip := net.IP(msg.Data[4:8]) 63 | port := binary.BigEndian.Uint16(msg.Data[2:4]) 64 | return &net.UDPAddr{IP: ip, Port: int(port)}, nil 65 | } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR { 66 | ip := net.IP(msg.Data[8:24]) 67 | port := binary.BigEndian.Uint16(msg.Data[2:4]) 68 | return &net.UDPAddr{IP: ip, Port: int(port)}, nil 69 | } 70 | } 71 | 72 | return nil, errors.New("cannot find origDst") 73 | } 74 | -------------------------------------------------------------------------------- /common/picker/picker.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Picker provides synchronization, and Context cancelation 10 | // for groups of goroutines working on subtasks of a common task. 11 | // Inspired by errGroup 12 | type Picker struct { 13 | ctx context.Context 14 | cancel func() 15 | 16 | wg sync.WaitGroup 17 | 18 | once sync.Once 19 | errOnce sync.Once 20 | result interface{} 21 | err error 22 | } 23 | 24 | func newPicker(ctx context.Context, cancel func()) *Picker { 25 | return &Picker{ 26 | ctx: ctx, 27 | cancel: cancel, 28 | } 29 | } 30 | 31 | // WithContext returns a new Picker and an associated Context derived from ctx. 32 | // and cancel when first element return. 33 | func WithContext(ctx context.Context) (*Picker, context.Context) { 34 | ctx, cancel := context.WithCancel(ctx) 35 | return newPicker(ctx, cancel), ctx 36 | } 37 | 38 | // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. 39 | func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { 40 | ctx, cancel := context.WithTimeout(ctx, timeout) 41 | return newPicker(ctx, cancel), ctx 42 | } 43 | 44 | // Wait blocks until all function calls from the Go method have returned, 45 | // then returns the first nil error result (if any) from them. 46 | func (p *Picker) Wait() interface{} { 47 | p.wg.Wait() 48 | if p.cancel != nil { 49 | p.cancel() 50 | } 51 | return p.result 52 | } 53 | 54 | // Error return the first error (if all success return nil) 55 | func (p *Picker) Error() error { 56 | return p.err 57 | } 58 | 59 | // Go calls the given function in a new goroutine. 60 | // The first call to return a nil error cancels the group; its result will be returned by Wait. 61 | func (p *Picker) Go(f func() (interface{}, error)) { 62 | p.wg.Add(1) 63 | 64 | go func() { 65 | defer p.wg.Done() 66 | 67 | if ret, err := f(); err == nil { 68 | p.once.Do(func() { 69 | p.result = ret 70 | if p.cancel != nil { 71 | p.cancel() 72 | } 73 | }) 74 | } else { 75 | p.errOnce.Do(func() { 76 | p.err = err 77 | }) 78 | } 79 | }() 80 | } 81 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Dreamacro/clash/common/observable" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var ( 13 | logCh = make(chan interface{}) 14 | source = observable.NewObservable(logCh) 15 | level = INFO 16 | ) 17 | 18 | func init() { 19 | log.SetOutput(os.Stdout) 20 | log.SetLevel(log.DebugLevel) 21 | } 22 | 23 | type Event struct { 24 | LogLevel LogLevel 25 | Payload string 26 | } 27 | 28 | func (e *Event) Type() string { 29 | return e.LogLevel.String() 30 | } 31 | 32 | func Infoln(format string, v ...interface{}) { 33 | event := newLog(INFO, format, v...) 34 | logCh <- event 35 | print(event) 36 | } 37 | 38 | func Warnln(format string, v ...interface{}) { 39 | event := newLog(WARNING, format, v...) 40 | logCh <- event 41 | print(event) 42 | } 43 | 44 | func Errorln(format string, v ...interface{}) { 45 | event := newLog(ERROR, format, v...) 46 | logCh <- event 47 | print(event) 48 | } 49 | 50 | func Debugln(format string, v ...interface{}) { 51 | event := newLog(DEBUG, format, v...) 52 | logCh <- event 53 | print(event) 54 | } 55 | 56 | func Fatalln(format string, v ...interface{}) { 57 | log.Fatalf(format, v...) 58 | } 59 | 60 | func Subscribe() observable.Subscription { 61 | sub, _ := source.Subscribe() 62 | return sub 63 | } 64 | 65 | func UnSubscribe(sub observable.Subscription) { 66 | source.UnSubscribe(sub) 67 | return 68 | } 69 | 70 | func Level() LogLevel { 71 | return level 72 | } 73 | 74 | func SetLevel(newLevel LogLevel) { 75 | level = newLevel 76 | } 77 | 78 | func print(data *Event) { 79 | if data.LogLevel < level { 80 | return 81 | } 82 | 83 | switch data.LogLevel { 84 | case INFO: 85 | log.Infoln(data.Payload) 86 | case WARNING: 87 | log.Warnln(data.Payload) 88 | case ERROR: 89 | log.Errorln(data.Payload) 90 | case DEBUG: 91 | log.Debugln(data.Payload) 92 | } 93 | } 94 | 95 | func newLog(logLevel LogLevel, format string, v ...interface{}) *Event { 96 | return &Event{ 97 | LogLevel: logLevel, 98 | Payload: fmt.Sprintf(format, v...), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /adapters/outbound/parser.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Dreamacro/clash/common/structure" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { 11 | decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) 12 | proxyType, existType := mapping["type"].(string) 13 | if !existType { 14 | return nil, fmt.Errorf("Missing type") 15 | } 16 | 17 | var proxy C.ProxyAdapter 18 | err := fmt.Errorf("Cannot parse") 19 | switch proxyType { 20 | case "ss": 21 | ssOption := &ShadowSocksOption{} 22 | err = decoder.Decode(mapping, ssOption) 23 | if err != nil { 24 | break 25 | } 26 | proxy, err = NewShadowSocks(*ssOption) 27 | case "ssr": 28 | ssrOption := &ShadowSocksROption{} 29 | err = decoder.Decode(mapping, ssrOption) 30 | if err != nil { 31 | break 32 | } 33 | proxy, err = NewShadowSocksR(*ssrOption) 34 | case "socks5": 35 | socksOption := &Socks5Option{} 36 | err = decoder.Decode(mapping, socksOption) 37 | if err != nil { 38 | break 39 | } 40 | proxy = NewSocks5(*socksOption) 41 | case "http": 42 | httpOption := &HttpOption{} 43 | err = decoder.Decode(mapping, httpOption) 44 | if err != nil { 45 | break 46 | } 47 | proxy = NewHttp(*httpOption) 48 | case "vmess": 49 | vmessOption := &VmessOption{ 50 | HTTPOpts: HTTPOptions{ 51 | Method: "GET", 52 | Path: []string{"/"}, 53 | }, 54 | } 55 | err = decoder.Decode(mapping, vmessOption) 56 | if err != nil { 57 | break 58 | } 59 | proxy, err = NewVmess(*vmessOption) 60 | case "snell": 61 | snellOption := &SnellOption{} 62 | err = decoder.Decode(mapping, snellOption) 63 | if err != nil { 64 | break 65 | } 66 | proxy, err = NewSnell(*snellOption) 67 | case "trojan": 68 | trojanOption := &TrojanOption{} 69 | err = decoder.Decode(mapping, trojanOption) 70 | if err != nil { 71 | break 72 | } 73 | proxy, err = NewTrojan(*trojanOption) 74 | default: 75 | return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) 76 | } 77 | 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | return NewProxy(proxy), nil 83 | } 84 | -------------------------------------------------------------------------------- /config/initial.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/Dreamacro/clash/component/mmdb" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | ) 13 | 14 | func downloadMMDB(path string) (err error) { 15 | resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb") 16 | if err != nil { 17 | return 18 | } 19 | defer resp.Body.Close() 20 | 21 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) 22 | if err != nil { 23 | return err 24 | } 25 | defer f.Close() 26 | _, err = io.Copy(f, resp.Body) 27 | 28 | return err 29 | } 30 | 31 | func initMMDB() error { 32 | if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { 33 | log.Infoln("Can't find MMDB, start download") 34 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 35 | return fmt.Errorf("Can't download MMDB: %s", err.Error()) 36 | } 37 | } 38 | 39 | if !mmdb.Verify() { 40 | log.Warnln("MMDB invalid, remove and download") 41 | if err := os.Remove(C.Path.MMDB()); err != nil { 42 | return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error()) 43 | } 44 | 45 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 46 | return fmt.Errorf("Can't download MMDB: %s", err.Error()) 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // Init prepare necessary files 54 | func Init(dir string) error { 55 | // initial homedir 56 | if _, err := os.Stat(dir); os.IsNotExist(err) { 57 | if err := os.MkdirAll(dir, 0777); err != nil { 58 | return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error()) 59 | } 60 | } 61 | 62 | // initial config.yaml 63 | if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { 64 | log.Infoln("Can't find config, create a initial config file") 65 | f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) 66 | if err != nil { 67 | return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error()) 68 | } 69 | f.Write([]byte(`port: 7890`)) 70 | f.Close() 71 | } 72 | 73 | // initial mmdb 74 | if err := initMMDB(); err != nil { 75 | return fmt.Errorf("Can't initial MMDB: %w", err) 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /hub/route/connections.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | T "github.com/Dreamacro/clash/tunnel" 11 | "github.com/gorilla/websocket" 12 | 13 | "github.com/go-chi/chi" 14 | "github.com/go-chi/render" 15 | ) 16 | 17 | func connectionRouter() http.Handler { 18 | r := chi.NewRouter() 19 | r.Get("/", getConnections) 20 | r.Delete("/", closeAllConnections) 21 | r.Delete("/{id}", closeConnection) 22 | return r 23 | } 24 | 25 | func getConnections(w http.ResponseWriter, r *http.Request) { 26 | if !websocket.IsWebSocketUpgrade(r) { 27 | snapshot := T.DefaultManager.Snapshot() 28 | render.JSON(w, r, snapshot) 29 | return 30 | } 31 | 32 | conn, err := upgrader.Upgrade(w, r, nil) 33 | if err != nil { 34 | return 35 | } 36 | 37 | intervalStr := r.URL.Query().Get("interval") 38 | interval := 1000 39 | if intervalStr != "" { 40 | t, err := strconv.Atoi(intervalStr) 41 | if err != nil { 42 | render.Status(r, http.StatusBadRequest) 43 | render.JSON(w, r, ErrBadRequest) 44 | return 45 | } 46 | 47 | interval = t 48 | } 49 | 50 | buf := &bytes.Buffer{} 51 | sendSnapshot := func() error { 52 | buf.Reset() 53 | snapshot := T.DefaultManager.Snapshot() 54 | if err := json.NewEncoder(buf).Encode(snapshot); err != nil { 55 | return err 56 | } 57 | 58 | return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) 59 | } 60 | 61 | if err := sendSnapshot(); err != nil { 62 | return 63 | } 64 | 65 | tick := time.NewTicker(time.Millisecond * time.Duration(interval)) 66 | defer tick.Stop() 67 | for range tick.C { 68 | if err := sendSnapshot(); err != nil { 69 | break 70 | } 71 | } 72 | } 73 | 74 | func closeConnection(w http.ResponseWriter, r *http.Request) { 75 | id := chi.URLParam(r, "id") 76 | snapshot := T.DefaultManager.Snapshot() 77 | for _, c := range snapshot.Connections { 78 | if id == c.ID() { 79 | c.Close() 80 | break 81 | } 82 | } 83 | render.NoContent(w, r) 84 | } 85 | 86 | func closeAllConnections(w http.ResponseWriter, r *http.Request) { 87 | snapshot := T.DefaultManager.Snapshot() 88 | for _, c := range snapshot.Connections { 89 | c.Close() 90 | } 91 | render.NoContent(w, r) 92 | } 93 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 感谢你向 Clash Core 提交 Feature Request! 12 | 在提交之前,请确认: 13 | 14 | - [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求 15 | 16 | 请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。 17 | 18 | 26 | 27 | 我都确认过了,我要继续提交。 28 | 29 | ------------------------------------------------------------------ 30 | 31 | 请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。 32 | 33 | 34 | ### Clash core config 35 | 39 | ``` 40 | …… 41 | ``` 42 | 43 | ### Clash log 44 | 48 | ``` 49 | …… 50 | ``` 51 | 52 | ### 环境 Environment 53 | 54 | * Clash Core 的操作系统 (the OS that the Clash core is running on) 55 | …… 56 | * 使用者的操作系统 (the OS running on the client) 57 | …… 58 | * 网路环境或拓扑 (network conditions/topology) 59 | …… 60 | * iptables,如果适用 (if applicable) 61 | …… 62 | * ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) 63 | …… 64 | * 其他 65 | …… 66 | 67 | ### 说明 Description 68 | 69 | 72 | 73 | ### 可能的解决方案 Possible Solution 74 | 75 | 76 | 77 | 78 | ### 更多信息 More Information 79 | -------------------------------------------------------------------------------- /component/ssr/protocol/auth_chain_b.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/Dreamacro/clash/component/ssr/tools" 7 | ) 8 | 9 | func init() { 10 | register("auth_chain_b", newAuthChainB) 11 | } 12 | 13 | func newAuthChainB(b *Base) Protocol { 14 | return &authChain{ 15 | Base: b, 16 | authData: &authData{}, 17 | salt: "auth_chain_b", 18 | hmac: tools.HmacMD5, 19 | hashDigest: tools.SHA1Sum, 20 | rnd: authChainBGetRandLen, 21 | } 22 | } 23 | 24 | func initDataSize(r *authChain) { 25 | random := &r.randomServer 26 | random.InitFromBin(r.Key) 27 | len := random.Next()%8 + 4 28 | r.dataSizeList = make([]int, len) 29 | for i := 0; i < int(len); i++ { 30 | r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) 31 | } 32 | sort.Ints(r.dataSizeList) 33 | 34 | len = random.Next()%16 + 8 35 | r.dataSizeList2 = make([]int, len) 36 | for i := 0; i < int(len); i++ { 37 | r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) 38 | } 39 | sort.Ints(r.dataSizeList2) 40 | } 41 | 42 | func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { 43 | if dataLength > 1440 { 44 | return 0 45 | } 46 | random.InitFromBinDatalen(lastHash[:16], dataLength) 47 | pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead }) 48 | finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList)) 49 | if finalPos < uint64(len(dataSizeList)) { 50 | return dataSizeList[finalPos] - dataLength - overhead 51 | } 52 | 53 | pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead }) 54 | finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2)) 55 | if finalPos < uint64(len(dataSizeList2)) { 56 | return dataSizeList2[finalPos] - dataLength - overhead 57 | } 58 | if finalPos < uint64(pos+len(dataSizeList2)-1) { 59 | return 0 60 | } 61 | 62 | if dataLength > 1300 { 63 | return int(random.Next() % 31) 64 | } 65 | if dataLength > 900 { 66 | return int(random.Next() % 127) 67 | } 68 | if dataLength > 400 { 69 | return int(random.Next() % 521) 70 | } 71 | return int(random.Next() % 1021) 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Clash 3 |
Clash
4 |

5 | 6 |

A rule-based tunnel in Go.

7 | 8 |

9 | 10 | Github Actions 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

19 | 20 | ## Features 21 | 22 | - Local HTTP/HTTPS/SOCKS server with authentication support 23 | - VMess, Shadowsocks, Trojan, Snell protocol support for remote connections 24 | - Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP. 25 | - Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes 26 | - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency 27 | - Remote providers, allowing users to get node lists remotely instead of hardcoding in config 28 | - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. 29 | - Comprehensive HTTP RESTful API controller 30 | 31 | ## Getting Started 32 | Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). 33 | 34 | ## Credits 35 | 36 | * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) 37 | * [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) 38 | 39 | ## License 40 | 41 | This software is released under the GPL-3.0 license. 42 | 43 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) 44 | 45 | ## TODO 46 | 47 | - [x] Complementing the necessary rule operators 48 | - [x] Redir proxy 49 | - [x] UDP support 50 | - [x] Connection manager 51 | - [ ] ~~Event API~~ 52 | -------------------------------------------------------------------------------- /tunnel/manager.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | var DefaultManager *Manager 9 | 10 | func init() { 11 | DefaultManager = &Manager{ 12 | upload: make(chan int64), 13 | download: make(chan int64), 14 | } 15 | DefaultManager.handle() 16 | } 17 | 18 | type Manager struct { 19 | connections sync.Map 20 | upload chan int64 21 | download chan int64 22 | uploadTemp int64 23 | downloadTemp int64 24 | uploadBlip int64 25 | downloadBlip int64 26 | uploadTotal int64 27 | downloadTotal int64 28 | } 29 | 30 | func (m *Manager) Join(c tracker) { 31 | m.connections.Store(c.ID(), c) 32 | } 33 | 34 | func (m *Manager) Leave(c tracker) { 35 | m.connections.Delete(c.ID()) 36 | } 37 | 38 | func (m *Manager) Upload() chan<- int64 { 39 | return m.upload 40 | } 41 | 42 | func (m *Manager) Download() chan<- int64 { 43 | return m.download 44 | } 45 | 46 | func (m *Manager) Now() (up int64, down int64) { 47 | return m.uploadBlip, m.downloadBlip 48 | } 49 | 50 | func (m *Manager) Snapshot() *Snapshot { 51 | connections := []tracker{} 52 | m.connections.Range(func(key, value interface{}) bool { 53 | connections = append(connections, value.(tracker)) 54 | return true 55 | }) 56 | 57 | return &Snapshot{ 58 | UploadTotal: m.uploadTotal, 59 | DownloadTotal: m.downloadTotal, 60 | Connections: connections, 61 | } 62 | } 63 | 64 | func (m *Manager) ResetStatistic() { 65 | m.uploadTemp = 0 66 | m.uploadBlip = 0 67 | m.uploadTotal = 0 68 | m.downloadTemp = 0 69 | m.downloadBlip = 0 70 | m.downloadTotal = 0 71 | } 72 | 73 | func (m *Manager) handle() { 74 | go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) 75 | go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) 76 | } 77 | 78 | func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) { 79 | ticker := time.NewTicker(time.Second) 80 | for { 81 | select { 82 | case n := <-ch: 83 | *temp += n 84 | *total += n 85 | case <-ticker.C: 86 | *blip = *temp 87 | *temp = 0 88 | } 89 | } 90 | } 91 | 92 | type Snapshot struct { 93 | DownloadTotal int64 `json:"downloadTotal"` 94 | UploadTotal int64 `json:"uploadTotal"` 95 | Connections []tracker `json:"connections"` 96 | } 97 | -------------------------------------------------------------------------------- /adapters/outboundgroup/fallback.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/Dreamacro/clash/adapters/outbound" 8 | "github.com/Dreamacro/clash/adapters/provider" 9 | "github.com/Dreamacro/clash/common/singledo" 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | type Fallback struct { 14 | *outbound.Base 15 | single *singledo.Single 16 | providers []provider.ProxyProvider 17 | } 18 | 19 | func (f *Fallback) Now() string { 20 | proxy := f.findAliveProxy() 21 | return proxy.Name() 22 | } 23 | 24 | func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 25 | proxy := f.findAliveProxy() 26 | c, err := proxy.DialContext(ctx, metadata) 27 | if err == nil { 28 | c.AppendToChains(f) 29 | } 30 | return c, err 31 | } 32 | 33 | func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 34 | proxy := f.findAliveProxy() 35 | pc, err := proxy.DialUDP(metadata) 36 | if err == nil { 37 | pc.AppendToChains(f) 38 | } 39 | return pc, err 40 | } 41 | 42 | func (f *Fallback) SupportUDP() bool { 43 | proxy := f.findAliveProxy() 44 | return proxy.SupportUDP() 45 | } 46 | 47 | func (f *Fallback) MarshalJSON() ([]byte, error) { 48 | var all []string 49 | for _, proxy := range f.proxies() { 50 | all = append(all, proxy.Name()) 51 | } 52 | return json.Marshal(map[string]interface{}{ 53 | "type": f.Type().String(), 54 | "now": f.Now(), 55 | "all": all, 56 | }) 57 | } 58 | 59 | func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { 60 | proxy := f.findAliveProxy() 61 | return proxy 62 | } 63 | 64 | func (f *Fallback) proxies() []C.Proxy { 65 | elm, _, _ := f.single.Do(func() (interface{}, error) { 66 | return getProvidersProxies(f.providers), nil 67 | }) 68 | 69 | return elm.([]C.Proxy) 70 | } 71 | 72 | func (f *Fallback) findAliveProxy() C.Proxy { 73 | proxies := f.proxies() 74 | for _, proxy := range proxies { 75 | if proxy.Alive() { 76 | return proxy 77 | } 78 | } 79 | 80 | return f.proxies()[0] 81 | } 82 | 83 | func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { 84 | return &Fallback{ 85 | Base: outbound.NewBase(name, "", C.Fallback, false), 86 | single: singledo.NewSingle(defaultGetProxiesDuration), 87 | providers: providers, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /component/snell/snell.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "sync" 11 | 12 | "github.com/Dreamacro/go-shadowsocks2/shadowaead" 13 | "golang.org/x/crypto/chacha20poly1305" 14 | ) 15 | 16 | const ( 17 | CommandPing byte = 0 18 | CommandConnect byte = 1 19 | 20 | CommandTunnel byte = 0 21 | CommandError byte = 2 22 | 23 | Version byte = 1 24 | ) 25 | 26 | var ( 27 | bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} 28 | ) 29 | 30 | type Snell struct { 31 | net.Conn 32 | buffer [1]byte 33 | reply bool 34 | } 35 | 36 | func (s *Snell) Read(b []byte) (int, error) { 37 | if s.reply { 38 | return s.Conn.Read(b) 39 | } 40 | 41 | s.reply = true 42 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { 43 | return 0, err 44 | } 45 | 46 | if s.buffer[0] == CommandTunnel { 47 | return s.Conn.Read(b) 48 | } else if s.buffer[0] != CommandError { 49 | return 0, errors.New("Command not support") 50 | } 51 | 52 | // CommandError 53 | // 1 byte error code 54 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { 55 | return 0, err 56 | } 57 | errcode := int(s.buffer[0]) 58 | 59 | // 1 byte error message length 60 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { 61 | return 0, err 62 | } 63 | length := int(s.buffer[0]) 64 | msg := make([]byte, length) 65 | 66 | if _, err := io.ReadFull(s.Conn, msg); err != nil { 67 | return 0, err 68 | } 69 | 70 | return 0, fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg)) 71 | } 72 | 73 | func WriteHeader(conn net.Conn, host string, port uint) error { 74 | buf := bufferPool.Get().(*bytes.Buffer) 75 | buf.Reset() 76 | defer bufferPool.Put(buf) 77 | buf.WriteByte(Version) 78 | buf.WriteByte(CommandConnect) 79 | 80 | // clientID length & id 81 | buf.WriteByte(0) 82 | 83 | // host & port 84 | buf.WriteByte(uint8(len(host))) 85 | buf.WriteString(host) 86 | binary.Write(buf, binary.BigEndian, uint16(port)) 87 | 88 | if _, err := conn.Write(buf.Bytes()); err != nil { 89 | return err 90 | } 91 | 92 | return nil 93 | } 94 | 95 | func StreamConn(conn net.Conn, psk []byte) net.Conn { 96 | cipher := &snellCipher{psk, chacha20poly1305.New} 97 | return &Snell{Conn: shadowaead.NewConn(conn, cipher)} 98 | } 99 | -------------------------------------------------------------------------------- /component/ssr/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac") 13 | errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") 14 | errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") 15 | errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large") 16 | errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") 17 | errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") 18 | errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") 19 | errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch") 20 | errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error") 21 | ) 22 | 23 | type authData struct { 24 | clientID []byte 25 | connectionID uint32 26 | mutex sync.Mutex 27 | } 28 | 29 | type recvInfo struct { 30 | recvID uint32 31 | buffer *bytes.Buffer 32 | } 33 | 34 | type hmacMethod func(key []byte, data []byte) []byte 35 | type hashDigestMethod func(data []byte) []byte 36 | type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int 37 | 38 | // Protocol provides methods for decoding, encoding and iv setting 39 | type Protocol interface { 40 | initForConn(iv []byte) Protocol 41 | GetProtocolOverhead() int 42 | SetOverhead(int) 43 | Decode([]byte) ([]byte, int, error) 44 | Encode([]byte) ([]byte, error) 45 | DecodePacket([]byte) ([]byte, int, error) 46 | EncodePacket([]byte) ([]byte, error) 47 | } 48 | 49 | type protocolCreator func(b *Base) Protocol 50 | 51 | var protocolList = make(map[string]protocolCreator) 52 | 53 | func register(name string, c protocolCreator) { 54 | protocolList[name] = c 55 | } 56 | 57 | // PickProtocol returns a protocol of the given name 58 | func PickProtocol(name string, b *Base) (Protocol, error) { 59 | if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok { 60 | return protocolCreator(b), nil 61 | } 62 | return nil, fmt.Errorf("Protocol %s not supported", name) 63 | } 64 | -------------------------------------------------------------------------------- /constant/metadata.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | // Socks addr type 10 | const ( 11 | AtypIPv4 = 1 12 | AtypDomainName = 3 13 | AtypIPv6 = 4 14 | 15 | TCP NetWork = iota 16 | UDP 17 | 18 | HTTP Type = iota 19 | HTTPCONNECT 20 | SOCKS 21 | REDIR 22 | ) 23 | 24 | type NetWork int 25 | 26 | func (n NetWork) String() string { 27 | if n == TCP { 28 | return "tcp" 29 | } 30 | return "udp" 31 | } 32 | 33 | func (n NetWork) MarshalJSON() ([]byte, error) { 34 | return json.Marshal(n.String()) 35 | } 36 | 37 | type Type int 38 | 39 | func (t Type) String() string { 40 | switch t { 41 | case HTTP: 42 | return "HTTP" 43 | case HTTPCONNECT: 44 | return "HTTP Connect" 45 | case SOCKS: 46 | return "Socks5" 47 | case REDIR: 48 | return "Redir" 49 | default: 50 | return "Unknown" 51 | } 52 | } 53 | 54 | func (t Type) MarshalJSON() ([]byte, error) { 55 | return json.Marshal(t.String()) 56 | } 57 | 58 | // Metadata is used to store connection address 59 | type Metadata struct { 60 | NetWork NetWork `json:"network"` 61 | Type Type `json:"type"` 62 | SrcIP net.IP `json:"sourceIP"` 63 | DstIP net.IP `json:"destinationIP"` 64 | SrcPort string `json:"sourcePort"` 65 | DstPort string `json:"destinationPort"` 66 | AddrType int `json:"-"` 67 | Host string `json:"host"` 68 | } 69 | 70 | func (m *Metadata) RemoteAddress() string { 71 | return net.JoinHostPort(m.String(), m.DstPort) 72 | } 73 | 74 | func (m *Metadata) SourceAddress() string { 75 | return net.JoinHostPort(m.SrcIP.String(), m.SrcPort) 76 | } 77 | 78 | func (m *Metadata) Resolved() bool { 79 | return m.DstIP != nil 80 | } 81 | 82 | func (m *Metadata) UDPAddr() *net.UDPAddr { 83 | if m.NetWork != UDP || m.DstIP == nil { 84 | return nil 85 | } 86 | port, _ := strconv.Atoi(m.DstPort) 87 | return &net.UDPAddr{ 88 | IP: m.DstIP, 89 | Port: port, 90 | } 91 | } 92 | 93 | func (m *Metadata) String() string { 94 | if m.Host != "" { 95 | return m.Host 96 | } else if m.DstIP != nil { 97 | return m.DstIP.String() 98 | } else { 99 | return "" 100 | } 101 | } 102 | 103 | func (m *Metadata) Valid() bool { 104 | return m.Host != "" || m.DstIP != nil 105 | } 106 | -------------------------------------------------------------------------------- /hub/route/provider.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/Dreamacro/clash/adapters/provider" 8 | "github.com/Dreamacro/clash/tunnel" 9 | 10 | "github.com/go-chi/chi" 11 | "github.com/go-chi/render" 12 | ) 13 | 14 | func proxyProviderRouter() http.Handler { 15 | r := chi.NewRouter() 16 | r.Get("/", getProviders) 17 | 18 | r.Route("/{name}", func(r chi.Router) { 19 | r.Use(parseProviderName, findProviderByName) 20 | r.Get("/", getProvider) 21 | r.Put("/", updateProvider) 22 | r.Get("/healthcheck", healthCheckProvider) 23 | }) 24 | return r 25 | } 26 | 27 | func getProviders(w http.ResponseWriter, r *http.Request) { 28 | providers := tunnel.Providers() 29 | render.JSON(w, r, render.M{ 30 | "providers": providers, 31 | }) 32 | } 33 | 34 | func getProvider(w http.ResponseWriter, r *http.Request) { 35 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 36 | render.JSON(w, r, provider) 37 | } 38 | 39 | func updateProvider(w http.ResponseWriter, r *http.Request) { 40 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 41 | if err := provider.Update(); err != nil { 42 | render.Status(r, http.StatusServiceUnavailable) 43 | render.JSON(w, r, newError(err.Error())) 44 | return 45 | } 46 | render.NoContent(w, r) 47 | } 48 | 49 | func healthCheckProvider(w http.ResponseWriter, r *http.Request) { 50 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 51 | provider.HealthCheck() 52 | render.NoContent(w, r) 53 | } 54 | 55 | func parseProviderName(next http.Handler) http.Handler { 56 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 57 | name := getEscapeParam(r, "name") 58 | ctx := context.WithValue(r.Context(), CtxKeyProviderName, name) 59 | next.ServeHTTP(w, r.WithContext(ctx)) 60 | }) 61 | } 62 | 63 | func findProviderByName(next http.Handler) http.Handler { 64 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 65 | name := r.Context().Value(CtxKeyProviderName).(string) 66 | providers := tunnel.Providers() 67 | provider, exist := providers[name] 68 | if !exist { 69 | render.Status(r, http.StatusNotFound) 70 | render.JSON(w, r, ErrNotFound) 71 | return 72 | } 73 | 74 | ctx := context.WithValue(r.Context(), CtxKeyProvider, provider) 75 | next.ServeHTTP(w, r.WithContext(ctx)) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /component/vmess/chunk.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | 8 | "github.com/Dreamacro/clash/common/pool" 9 | ) 10 | 11 | const ( 12 | lenSize = 2 13 | chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 14 | maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() 15 | ) 16 | 17 | type chunkReader struct { 18 | io.Reader 19 | buf []byte 20 | sizeBuf []byte 21 | offset int 22 | } 23 | 24 | func newChunkReader(reader io.Reader) *chunkReader { 25 | return &chunkReader{Reader: reader, sizeBuf: make([]byte, lenSize)} 26 | } 27 | 28 | func newChunkWriter(writer io.WriteCloser) *chunkWriter { 29 | return &chunkWriter{Writer: writer} 30 | } 31 | 32 | func (cr *chunkReader) Read(b []byte) (int, error) { 33 | if cr.buf != nil { 34 | n := copy(b, cr.buf[cr.offset:]) 35 | cr.offset += n 36 | if cr.offset == len(cr.buf) { 37 | pool.Put(cr.buf) 38 | cr.buf = nil 39 | } 40 | return n, nil 41 | } 42 | 43 | _, err := io.ReadFull(cr.Reader, cr.sizeBuf) 44 | if err != nil { 45 | return 0, err 46 | } 47 | 48 | size := int(binary.BigEndian.Uint16(cr.sizeBuf)) 49 | if size > maxSize { 50 | return 0, errors.New("Buffer is larger than standard") 51 | } 52 | 53 | if len(b) >= size { 54 | _, err := io.ReadFull(cr.Reader, b[:size]) 55 | if err != nil { 56 | return 0, err 57 | } 58 | 59 | return size, nil 60 | } 61 | 62 | buf := pool.Get(size) 63 | _, err = io.ReadFull(cr.Reader, buf) 64 | if err != nil { 65 | pool.Put(buf) 66 | return 0, err 67 | } 68 | n := copy(b, buf) 69 | cr.offset = n 70 | cr.buf = buf 71 | return n, nil 72 | } 73 | 74 | type chunkWriter struct { 75 | io.Writer 76 | } 77 | 78 | func (cw *chunkWriter) Write(b []byte) (n int, err error) { 79 | buf := pool.Get(pool.RelayBufferSize) 80 | defer pool.Put(buf) 81 | length := len(b) 82 | for { 83 | if length == 0 { 84 | break 85 | } 86 | readLen := chunkSize 87 | if length < chunkSize { 88 | readLen = length 89 | } 90 | payloadBuf := buf[lenSize : lenSize+chunkSize] 91 | copy(payloadBuf, b[n:n+readLen]) 92 | 93 | binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen)) 94 | _, err = cw.Writer.Write(buf[:lenSize+readLen]) 95 | if err != nil { 96 | break 97 | } 98 | n += readLen 99 | length -= readLen 100 | } 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /dns/doh.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | 11 | "github.com/Dreamacro/clash/component/dialer" 12 | 13 | D "github.com/miekg/dns" 14 | ) 15 | 16 | const ( 17 | // dotMimeType is the DoH mimetype that should be used. 18 | dotMimeType = "application/dns-message" 19 | ) 20 | 21 | type dohClient struct { 22 | url string 23 | transport *http.Transport 24 | } 25 | 26 | func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { 27 | return dc.ExchangeContext(context.Background(), m) 28 | } 29 | 30 | func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 31 | req, err := dc.newRequest(m) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | req = req.WithContext(ctx) 37 | return dc.doRequest(req) 38 | } 39 | 40 | // newRequest returns a new DoH request given a dns.Msg. 41 | func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) { 42 | buf, err := m.Pack() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf)) 48 | if err != nil { 49 | return req, err 50 | } 51 | 52 | req.Header.Set("content-type", dotMimeType) 53 | req.Header.Set("accept", dotMimeType) 54 | return req, nil 55 | } 56 | 57 | func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { 58 | client := &http.Client{Transport: dc.transport} 59 | resp, err := client.Do(req) 60 | if err != nil { 61 | return nil, err 62 | } 63 | defer resp.Body.Close() 64 | 65 | buf, err := ioutil.ReadAll(resp.Body) 66 | if err != nil { 67 | return nil, err 68 | } 69 | msg = &D.Msg{} 70 | err = msg.Unpack(buf) 71 | return msg, err 72 | } 73 | 74 | func newDoHClient(url string, r *Resolver) *dohClient { 75 | return &dohClient{ 76 | url: url, 77 | transport: &http.Transport{ 78 | TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, 79 | ForceAttemptHTTP2: true, 80 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 81 | host, port, err := net.SplitHostPort(addr) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | ip, err := r.ResolveIPv4(host) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) 92 | }, 93 | }, 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /common/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Cache store element with a expired time 10 | type Cache struct { 11 | *cache 12 | } 13 | 14 | type cache struct { 15 | mapping sync.Map 16 | janitor *janitor 17 | } 18 | 19 | type element struct { 20 | Expired time.Time 21 | Payload interface{} 22 | } 23 | 24 | // Put element in Cache with its ttl 25 | func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) { 26 | c.mapping.Store(key, &element{ 27 | Payload: payload, 28 | Expired: time.Now().Add(ttl), 29 | }) 30 | } 31 | 32 | // Get element in Cache, and drop when it expired 33 | func (c *cache) Get(key interface{}) interface{} { 34 | item, exist := c.mapping.Load(key) 35 | if !exist { 36 | return nil 37 | } 38 | elm := item.(*element) 39 | // expired 40 | if time.Since(elm.Expired) > 0 { 41 | c.mapping.Delete(key) 42 | return nil 43 | } 44 | return elm.Payload 45 | } 46 | 47 | // GetWithExpire element in Cache with Expire Time 48 | func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) { 49 | item, exist := c.mapping.Load(key) 50 | if !exist { 51 | return 52 | } 53 | elm := item.(*element) 54 | // expired 55 | if time.Since(elm.Expired) > 0 { 56 | c.mapping.Delete(key) 57 | return 58 | } 59 | return elm.Payload, elm.Expired 60 | } 61 | 62 | func (c *cache) cleanup() { 63 | c.mapping.Range(func(k, v interface{}) bool { 64 | key := k.(string) 65 | elm := v.(*element) 66 | if time.Since(elm.Expired) > 0 { 67 | c.mapping.Delete(key) 68 | } 69 | return true 70 | }) 71 | } 72 | 73 | type janitor struct { 74 | interval time.Duration 75 | stop chan struct{} 76 | } 77 | 78 | func (j *janitor) process(c *cache) { 79 | ticker := time.NewTicker(j.interval) 80 | for { 81 | select { 82 | case <-ticker.C: 83 | c.cleanup() 84 | case <-j.stop: 85 | ticker.Stop() 86 | return 87 | } 88 | } 89 | } 90 | 91 | func stopJanitor(c *Cache) { 92 | c.janitor.stop <- struct{}{} 93 | } 94 | 95 | // New return *Cache 96 | func New(interval time.Duration) *Cache { 97 | j := &janitor{ 98 | interval: interval, 99 | stop: make(chan struct{}), 100 | } 101 | c := &cache{janitor: j} 102 | go j.process(c) 103 | C := &Cache{c} 104 | runtime.SetFinalizer(C, stopJanitor) 105 | return C 106 | } 107 | -------------------------------------------------------------------------------- /component/simple-obfs/http.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | 12 | "github.com/Dreamacro/clash/common/pool" 13 | ) 14 | 15 | // HTTPObfs is shadowsocks http simple-obfs implementation 16 | type HTTPObfs struct { 17 | net.Conn 18 | host string 19 | port string 20 | buf []byte 21 | offset int 22 | firstRequest bool 23 | firstResponse bool 24 | } 25 | 26 | func (ho *HTTPObfs) Read(b []byte) (int, error) { 27 | if ho.buf != nil { 28 | n := copy(b, ho.buf[ho.offset:]) 29 | ho.offset += n 30 | if ho.offset == len(ho.buf) { 31 | pool.Put(ho.buf) 32 | ho.buf = nil 33 | } 34 | return n, nil 35 | } 36 | 37 | if ho.firstResponse { 38 | buf := pool.Get(pool.RelayBufferSize) 39 | n, err := ho.Conn.Read(buf) 40 | if err != nil { 41 | pool.Put(buf) 42 | return 0, err 43 | } 44 | idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) 45 | if idx == -1 { 46 | pool.Put(buf) 47 | return 0, io.EOF 48 | } 49 | ho.firstResponse = false 50 | length := n - (idx + 4) 51 | n = copy(b, buf[idx+4:n]) 52 | if length > n { 53 | ho.buf = buf[:idx+4+length] 54 | ho.offset = idx + 4 + n 55 | } else { 56 | pool.Put(buf) 57 | } 58 | return n, nil 59 | } 60 | return ho.Conn.Read(b) 61 | } 62 | 63 | func (ho *HTTPObfs) Write(b []byte) (int, error) { 64 | if ho.firstRequest { 65 | randBytes := make([]byte, 16) 66 | rand.Read(randBytes) 67 | req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:])) 68 | req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2)) 69 | req.Header.Set("Upgrade", "websocket") 70 | req.Header.Set("Connection", "Upgrade") 71 | req.Host = ho.host 72 | if ho.port != "80" { 73 | req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) 74 | } 75 | req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) 76 | req.ContentLength = int64(len(b)) 77 | err := req.Write(ho.Conn) 78 | ho.firstRequest = false 79 | return len(b), err 80 | } 81 | 82 | return ho.Conn.Write(b) 83 | } 84 | 85 | // NewHTTPObfs return a HTTPObfs 86 | func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { 87 | return &HTTPObfs{ 88 | Conn: conn, 89 | firstRequest: true, 90 | firstResponse: true, 91 | host: host, 92 | port: port, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /adapters/outbound/snell.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | 9 | "github.com/Dreamacro/clash/common/structure" 10 | "github.com/Dreamacro/clash/component/dialer" 11 | obfs "github.com/Dreamacro/clash/component/simple-obfs" 12 | "github.com/Dreamacro/clash/component/snell" 13 | C "github.com/Dreamacro/clash/constant" 14 | ) 15 | 16 | type Snell struct { 17 | *Base 18 | psk []byte 19 | obfsOption *simpleObfsOption 20 | } 21 | 22 | type SnellOption struct { 23 | Name string `proxy:"name"` 24 | Server string `proxy:"server"` 25 | Port int `proxy:"port"` 26 | Psk string `proxy:"psk"` 27 | ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` 28 | } 29 | 30 | func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 31 | switch s.obfsOption.Mode { 32 | case "tls": 33 | c = obfs.NewTLSObfs(c, s.obfsOption.Host) 34 | case "http": 35 | _, port, _ := net.SplitHostPort(s.addr) 36 | c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) 37 | } 38 | c = snell.StreamConn(c, s.psk) 39 | port, _ := strconv.Atoi(metadata.DstPort) 40 | err := snell.WriteHeader(c, metadata.String(), uint(port)) 41 | return c, err 42 | } 43 | 44 | func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 45 | c, err := dialer.DialContext(ctx, "tcp", s.addr) 46 | if err != nil { 47 | return nil, fmt.Errorf("%s connect error: %w", s.addr, err) 48 | } 49 | tcpKeepAlive(c) 50 | 51 | c, err = s.StreamConn(c, metadata) 52 | return NewConn(c, s), err 53 | } 54 | 55 | func NewSnell(option SnellOption) (*Snell, error) { 56 | addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 57 | psk := []byte(option.Psk) 58 | 59 | decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) 60 | obfsOption := &simpleObfsOption{Host: "bing.com"} 61 | if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { 62 | return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) 63 | } 64 | 65 | if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { 66 | return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) 67 | } 68 | 69 | return &Snell{ 70 | Base: &Base{ 71 | name: option.Name, 72 | addr: addr, 73 | tp: C.Snell, 74 | }, 75 | psk: psk, 76 | obfsOption: obfsOption, 77 | }, nil 78 | } 79 | -------------------------------------------------------------------------------- /proxy/redir/utils_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package redir 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | "os" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | // dialUDP acts like net.DialUDP for transparent proxy. 14 | // It binds to a non-local address(`lAddr`). 15 | func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { 16 | rSockAddr, err := udpAddrToSockAddr(rAddr) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | lSockAddr, err := udpAddrToSockAddr(lAddr) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { 32 | syscall.Close(fd) 33 | return nil, err 34 | } 35 | 36 | if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { 37 | syscall.Close(fd) 38 | return nil, err 39 | } 40 | 41 | if err = syscall.Bind(fd, lSockAddr); err != nil { 42 | syscall.Close(fd) 43 | return nil, err 44 | } 45 | 46 | if err = syscall.Connect(fd, rSockAddr); err != nil { 47 | syscall.Close(fd) 48 | return nil, err 49 | } 50 | 51 | fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String())) 52 | defer fdFile.Close() 53 | 54 | c, err := net.FileConn(fdFile) 55 | if err != nil { 56 | syscall.Close(fd) 57 | return nil, err 58 | } 59 | 60 | return c.(*net.UDPConn), nil 61 | } 62 | 63 | func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { 64 | switch { 65 | case addr.IP.To4() != nil: 66 | ip := [4]byte{} 67 | copy(ip[:], addr.IP.To4()) 68 | 69 | return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil 70 | 71 | default: 72 | ip := [16]byte{} 73 | copy(ip[:], addr.IP.To16()) 74 | 75 | zoneID, err := strconv.ParseUint(addr.Zone, 10, 32) 76 | if err != nil { 77 | zoneID = 0 78 | } 79 | 80 | return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil 81 | } 82 | } 83 | 84 | func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { 85 | switch net[len(net)-1] { 86 | case '4': 87 | return syscall.AF_INET 88 | case '6': 89 | return syscall.AF_INET6 90 | } 91 | 92 | if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { 93 | return syscall.AF_INET 94 | } 95 | return syscall.AF_INET6 96 | } 97 | -------------------------------------------------------------------------------- /adapters/outbound/util.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "strconv" 10 | "sync" 11 | "time" 12 | 13 | "github.com/Dreamacro/clash/component/resolver" 14 | "github.com/Dreamacro/clash/component/socks5" 15 | C "github.com/Dreamacro/clash/constant" 16 | ) 17 | 18 | const ( 19 | tcpTimeout = 5 * time.Second 20 | ) 21 | 22 | var ( 23 | globalClientSessionCache tls.ClientSessionCache 24 | once sync.Once 25 | ) 26 | 27 | func urlToMetadata(rawURL string) (addr C.Metadata, err error) { 28 | u, err := url.Parse(rawURL) 29 | if err != nil { 30 | return 31 | } 32 | 33 | port := u.Port() 34 | if port == "" { 35 | switch u.Scheme { 36 | case "https": 37 | port = "443" 38 | case "http": 39 | port = "80" 40 | default: 41 | err = fmt.Errorf("%s scheme not Support", rawURL) 42 | return 43 | } 44 | } 45 | 46 | addr = C.Metadata{ 47 | AddrType: C.AtypDomainName, 48 | Host: u.Hostname(), 49 | DstIP: nil, 50 | DstPort: port, 51 | } 52 | return 53 | } 54 | 55 | func tcpKeepAlive(c net.Conn) { 56 | if tcp, ok := c.(*net.TCPConn); ok { 57 | tcp.SetKeepAlive(true) 58 | tcp.SetKeepAlivePeriod(30 * time.Second) 59 | } 60 | } 61 | 62 | func getClientSessionCache() tls.ClientSessionCache { 63 | once.Do(func() { 64 | globalClientSessionCache = tls.NewLRUClientSessionCache(128) 65 | }) 66 | return globalClientSessionCache 67 | } 68 | 69 | func serializesSocksAddr(metadata *C.Metadata) []byte { 70 | var buf [][]byte 71 | aType := uint8(metadata.AddrType) 72 | p, _ := strconv.Atoi(metadata.DstPort) 73 | port := []byte{uint8(p >> 8), uint8(p & 0xff)} 74 | switch metadata.AddrType { 75 | case socks5.AtypDomainName: 76 | len := uint8(len(metadata.Host)) 77 | host := []byte(metadata.Host) 78 | buf = [][]byte{{aType, len}, host, port} 79 | case socks5.AtypIPv4: 80 | host := metadata.DstIP.To4() 81 | buf = [][]byte{{aType}, host, port} 82 | case socks5.AtypIPv6: 83 | host := metadata.DstIP.To16() 84 | buf = [][]byte{{aType}, host, port} 85 | } 86 | return bytes.Join(buf, nil) 87 | } 88 | 89 | func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { 90 | host, port, err := net.SplitHostPort(address) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | ip, err := resolver.ResolveIP(host) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 100 | } 101 | -------------------------------------------------------------------------------- /adapters/outboundgroup/selector.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/Dreamacro/clash/adapters/outbound" 9 | "github.com/Dreamacro/clash/adapters/provider" 10 | "github.com/Dreamacro/clash/common/singledo" 11 | C "github.com/Dreamacro/clash/constant" 12 | ) 13 | 14 | type Selector struct { 15 | *outbound.Base 16 | single *singledo.Single 17 | selected string 18 | providers []provider.ProxyProvider 19 | } 20 | 21 | func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 22 | c, err := s.selectedProxy().DialContext(ctx, metadata) 23 | if err == nil { 24 | c.AppendToChains(s) 25 | } 26 | return c, err 27 | } 28 | 29 | func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 30 | pc, err := s.selectedProxy().DialUDP(metadata) 31 | if err == nil { 32 | pc.AppendToChains(s) 33 | } 34 | return pc, err 35 | } 36 | 37 | func (s *Selector) SupportUDP() bool { 38 | return s.selectedProxy().SupportUDP() 39 | } 40 | 41 | func (s *Selector) MarshalJSON() ([]byte, error) { 42 | var all []string 43 | for _, proxy := range getProvidersProxies(s.providers) { 44 | all = append(all, proxy.Name()) 45 | } 46 | 47 | return json.Marshal(map[string]interface{}{ 48 | "type": s.Type().String(), 49 | "now": s.Now(), 50 | "all": all, 51 | }) 52 | } 53 | 54 | func (s *Selector) Now() string { 55 | return s.selectedProxy().Name() 56 | } 57 | 58 | func (s *Selector) Set(name string) error { 59 | for _, proxy := range getProvidersProxies(s.providers) { 60 | if proxy.Name() == name { 61 | s.selected = name 62 | s.single.Reset() 63 | return nil 64 | } 65 | } 66 | 67 | return errors.New("Proxy does not exist") 68 | } 69 | 70 | func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { 71 | return s.selectedProxy() 72 | } 73 | 74 | func (s *Selector) selectedProxy() C.Proxy { 75 | elm, _, _ := s.single.Do(func() (interface{}, error) { 76 | proxies := getProvidersProxies(s.providers) 77 | for _, proxy := range proxies { 78 | if proxy.Name() == s.selected { 79 | return proxy, nil 80 | } 81 | } 82 | 83 | return proxies[0], nil 84 | }) 85 | 86 | return elm.(C.Proxy) 87 | } 88 | 89 | func NewSelector(name string, providers []provider.ProxyProvider) *Selector { 90 | selected := providers[0].Proxies()[0].Name() 91 | return &Selector{ 92 | Base: outbound.NewBase(name, "", C.Selector, false), 93 | single: singledo.NewSingle(defaultGetProxiesDuration), 94 | providers: providers, 95 | selected: selected, 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /adapters/outboundgroup/relay.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/Dreamacro/clash/adapters/outbound" 10 | "github.com/Dreamacro/clash/adapters/provider" 11 | "github.com/Dreamacro/clash/common/singledo" 12 | "github.com/Dreamacro/clash/component/dialer" 13 | C "github.com/Dreamacro/clash/constant" 14 | ) 15 | 16 | type Relay struct { 17 | *outbound.Base 18 | single *singledo.Single 19 | providers []provider.ProxyProvider 20 | } 21 | 22 | func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 23 | proxies := r.proxies(metadata) 24 | if len(proxies) == 0 { 25 | return nil, errors.New("Proxy does not exist") 26 | } 27 | first := proxies[0] 28 | last := proxies[len(proxies)-1] 29 | 30 | c, err := dialer.DialContext(ctx, "tcp", first.Addr()) 31 | if err != nil { 32 | return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) 33 | } 34 | tcpKeepAlive(c) 35 | 36 | var currentMeta *C.Metadata 37 | for _, proxy := range proxies[1:] { 38 | currentMeta, err = addrToMetadata(proxy.Addr()) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | c, err = first.StreamConn(c, currentMeta) 44 | if err != nil { 45 | return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) 46 | } 47 | 48 | first = proxy 49 | } 50 | 51 | c, err = last.StreamConn(c, metadata) 52 | if err != nil { 53 | return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) 54 | } 55 | 56 | return outbound.NewConn(c, r), nil 57 | } 58 | 59 | func (r *Relay) MarshalJSON() ([]byte, error) { 60 | var all []string 61 | for _, proxy := range r.rawProxies() { 62 | all = append(all, proxy.Name()) 63 | } 64 | return json.Marshal(map[string]interface{}{ 65 | "type": r.Type().String(), 66 | "all": all, 67 | }) 68 | } 69 | 70 | func (r *Relay) rawProxies() []C.Proxy { 71 | elm, _, _ := r.single.Do(func() (interface{}, error) { 72 | return getProvidersProxies(r.providers), nil 73 | }) 74 | 75 | return elm.([]C.Proxy) 76 | } 77 | 78 | func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy { 79 | proxies := r.rawProxies() 80 | 81 | for n, proxy := range proxies { 82 | subproxy := proxy.Unwrap(metadata) 83 | for subproxy != nil { 84 | proxies[n] = subproxy 85 | subproxy = subproxy.Unwrap(metadata) 86 | } 87 | } 88 | 89 | return proxies 90 | } 91 | 92 | func NewRelay(name string, providers []provider.ProxyProvider) *Relay { 93 | return &Relay{ 94 | Base: outbound.NewBase(name, "", C.Relay, false), 95 | single: singledo.NewSingle(defaultGetProxiesDuration), 96 | providers: providers, 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /adapters/provider/vehicle.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/Dreamacro/clash/component/dialer" 11 | ) 12 | 13 | // Vehicle Type 14 | const ( 15 | File VehicleType = iota 16 | HTTP 17 | Compatible 18 | ) 19 | 20 | // VehicleType defined 21 | type VehicleType int 22 | 23 | func (v VehicleType) String() string { 24 | switch v { 25 | case File: 26 | return "File" 27 | case HTTP: 28 | return "HTTP" 29 | case Compatible: 30 | return "Compatible" 31 | default: 32 | return "Unknown" 33 | } 34 | } 35 | 36 | type Vehicle interface { 37 | Read() ([]byte, error) 38 | Path() string 39 | Type() VehicleType 40 | } 41 | 42 | type FileVehicle struct { 43 | path string 44 | } 45 | 46 | func (f *FileVehicle) Type() VehicleType { 47 | return File 48 | } 49 | 50 | func (f *FileVehicle) Path() string { 51 | return f.path 52 | } 53 | 54 | func (f *FileVehicle) Read() ([]byte, error) { 55 | return ioutil.ReadFile(f.path) 56 | } 57 | 58 | func NewFileVehicle(path string) *FileVehicle { 59 | return &FileVehicle{path: path} 60 | } 61 | 62 | type HTTPVehicle struct { 63 | url string 64 | path string 65 | } 66 | 67 | func (h *HTTPVehicle) Type() VehicleType { 68 | return HTTP 69 | } 70 | 71 | func (h *HTTPVehicle) Path() string { 72 | return h.path 73 | } 74 | 75 | func (h *HTTPVehicle) Read() ([]byte, error) { 76 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 77 | defer cancel() 78 | 79 | uri, err := url.Parse(h.url) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | req, err := http.NewRequest(http.MethodGet, uri.String(), nil) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if user := uri.User; user != nil { 90 | password, _ := user.Password() 91 | req.SetBasicAuth(user.Username(), password) 92 | } 93 | 94 | req = req.WithContext(ctx) 95 | 96 | transport := &http.Transport{ 97 | // from http.DefaultTransport 98 | MaxIdleConns: 100, 99 | IdleConnTimeout: 90 * time.Second, 100 | TLSHandshakeTimeout: 10 * time.Second, 101 | ExpectContinueTimeout: 1 * time.Second, 102 | DialContext: dialer.DialContext, 103 | } 104 | 105 | client := http.Client{Transport: transport} 106 | resp, err := client.Do(req) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | buf, err := ioutil.ReadAll(resp.Body) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | return buf, nil 117 | } 118 | 119 | func NewHTTPVehicle(url string, path string) *HTTPVehicle { 120 | return &HTTPVehicle{url, path} 121 | } 122 | -------------------------------------------------------------------------------- /component/trie/domain_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var localIP = net.IP{127, 0, 0, 1} 11 | 12 | func TestTrie_Basic(t *testing.T) { 13 | tree := New() 14 | domains := []string{ 15 | "example.com", 16 | "google.com", 17 | "localhost", 18 | } 19 | 20 | for _, domain := range domains { 21 | tree.Insert(domain, localIP) 22 | } 23 | 24 | node := tree.Search("example.com") 25 | assert.NotNil(t, node) 26 | assert.True(t, node.Data.(net.IP).Equal(localIP)) 27 | assert.NotNil(t, tree.Insert("", localIP)) 28 | assert.Nil(t, tree.Search("")) 29 | assert.NotNil(t, tree.Search("localhost")) 30 | assert.Nil(t, tree.Search("www.google.com")) 31 | } 32 | 33 | func TestTrie_Wildcard(t *testing.T) { 34 | tree := New() 35 | domains := []string{ 36 | "*.example.com", 37 | "sub.*.example.com", 38 | "*.dev", 39 | ".org", 40 | ".example.net", 41 | ".apple.*", 42 | "+.foo.com", 43 | "+.stun.*.*", 44 | "+.stun.*.*.*", 45 | "+.stun.*.*.*.*", 46 | "stun.l.google.com", 47 | } 48 | 49 | for _, domain := range domains { 50 | tree.Insert(domain, localIP) 51 | } 52 | 53 | assert.NotNil(t, tree.Search("sub.example.com")) 54 | assert.NotNil(t, tree.Search("sub.foo.example.com")) 55 | assert.NotNil(t, tree.Search("test.org")) 56 | assert.NotNil(t, tree.Search("test.example.net")) 57 | assert.NotNil(t, tree.Search("test.apple.com")) 58 | assert.NotNil(t, tree.Search("test.foo.com")) 59 | assert.NotNil(t, tree.Search("foo.com")) 60 | assert.NotNil(t, tree.Search("global.stun.website.com")) 61 | assert.Nil(t, tree.Search("foo.sub.example.com")) 62 | assert.Nil(t, tree.Search("foo.example.dev")) 63 | assert.Nil(t, tree.Search("example.com")) 64 | } 65 | 66 | func TestTrie_Priority(t *testing.T) { 67 | tree := New() 68 | domains := []string{ 69 | ".dev", 70 | "example.dev", 71 | "*.example.dev", 72 | "test.example.dev", 73 | } 74 | 75 | assertFn := func(domain string, data int) { 76 | node := tree.Search(domain) 77 | assert.NotNil(t, node) 78 | assert.Equal(t, data, node.Data) 79 | } 80 | 81 | for idx, domain := range domains { 82 | tree.Insert(domain, idx) 83 | } 84 | 85 | assertFn("test.dev", 0) 86 | assertFn("foo.bar.dev", 0) 87 | assertFn("example.dev", 1) 88 | assertFn("foo.example.dev", 2) 89 | assertFn("test.example.dev", 3) 90 | } 91 | 92 | func TestTrie_Boundary(t *testing.T) { 93 | tree := New() 94 | tree.Insert("*.dev", localIP) 95 | 96 | assert.NotNil(t, tree.Insert(".", localIP)) 97 | assert.NotNil(t, tree.Insert("..dev", localIP)) 98 | assert.Nil(t, tree.Search("dev")) 99 | } 100 | -------------------------------------------------------------------------------- /common/observable/observable_test.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func iterator(item []interface{}) chan interface{} { 13 | ch := make(chan interface{}) 14 | go func() { 15 | time.Sleep(100 * time.Millisecond) 16 | for _, elm := range item { 17 | ch <- elm 18 | } 19 | close(ch) 20 | }() 21 | return ch 22 | } 23 | 24 | func TestObservable(t *testing.T) { 25 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 26 | src := NewObservable(iter) 27 | data, err := src.Subscribe() 28 | assert.Nil(t, err) 29 | count := 0 30 | for range data { 31 | count++ 32 | } 33 | assert.Equal(t, count, 5) 34 | } 35 | 36 | func TestObservable_MutilSubscribe(t *testing.T) { 37 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 38 | src := NewObservable(iter) 39 | ch1, _ := src.Subscribe() 40 | ch2, _ := src.Subscribe() 41 | var count int32 42 | 43 | var wg sync.WaitGroup 44 | wg.Add(2) 45 | waitCh := func(ch <-chan interface{}) { 46 | for range ch { 47 | atomic.AddInt32(&count, 1) 48 | } 49 | wg.Done() 50 | } 51 | go waitCh(ch1) 52 | go waitCh(ch2) 53 | wg.Wait() 54 | assert.Equal(t, int32(10), count) 55 | } 56 | 57 | func TestObservable_UnSubscribe(t *testing.T) { 58 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 59 | src := NewObservable(iter) 60 | data, err := src.Subscribe() 61 | assert.Nil(t, err) 62 | src.UnSubscribe(data) 63 | _, open := <-data 64 | assert.False(t, open) 65 | } 66 | 67 | func TestObservable_SubscribeClosedSource(t *testing.T) { 68 | iter := iterator([]interface{}{1}) 69 | src := NewObservable(iter) 70 | data, _ := src.Subscribe() 71 | <-data 72 | 73 | _, closed := src.Subscribe() 74 | assert.NotNil(t, closed) 75 | } 76 | 77 | func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { 78 | sub := Subscription(make(chan interface{})) 79 | iter := iterator([]interface{}{1}) 80 | src := NewObservable(iter) 81 | src.UnSubscribe(sub) 82 | } 83 | 84 | func TestObservable_SubscribeGoroutineLeak(t *testing.T) { 85 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 86 | src := NewObservable(iter) 87 | max := 100 88 | 89 | var list []Subscription 90 | for i := 0; i < max; i++ { 91 | ch, _ := src.Subscribe() 92 | list = append(list, ch) 93 | } 94 | 95 | var wg sync.WaitGroup 96 | wg.Add(max) 97 | waitCh := func(ch <-chan interface{}) { 98 | for range ch { 99 | } 100 | wg.Done() 101 | } 102 | 103 | for _, ch := range list { 104 | go waitCh(ch) 105 | } 106 | wg.Wait() 107 | 108 | for _, sub := range list { 109 | _, more := <-sub 110 | assert.False(t, more) 111 | } 112 | 113 | _, more := <-list[0] 114 | assert.False(t, more) 115 | } 116 | -------------------------------------------------------------------------------- /component/vmess/vmess.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "runtime" 9 | "sync" 10 | 11 | "github.com/gofrs/uuid" 12 | ) 13 | 14 | // Version of vmess 15 | const Version byte = 1 16 | 17 | // Request Options 18 | const ( 19 | OptionChunkStream byte = 1 20 | OptionChunkMasking byte = 4 21 | ) 22 | 23 | // Security type vmess 24 | type Security = byte 25 | 26 | // Cipher types 27 | const ( 28 | SecurityAES128GCM Security = 3 29 | SecurityCHACHA20POLY1305 Security = 4 30 | SecurityNone Security = 5 31 | ) 32 | 33 | // CipherMapping return 34 | var CipherMapping = map[string]byte{ 35 | "none": SecurityNone, 36 | "aes-128-gcm": SecurityAES128GCM, 37 | "chacha20-poly1305": SecurityCHACHA20POLY1305, 38 | } 39 | 40 | var ( 41 | clientSessionCache tls.ClientSessionCache 42 | once sync.Once 43 | ) 44 | 45 | // Command types 46 | const ( 47 | CommandTCP byte = 1 48 | CommandUDP byte = 2 49 | ) 50 | 51 | // Addr types 52 | const ( 53 | AtypIPv4 byte = 1 54 | AtypDomainName byte = 2 55 | AtypIPv6 byte = 3 56 | ) 57 | 58 | // DstAddr store destination address 59 | type DstAddr struct { 60 | UDP bool 61 | AddrType byte 62 | Addr []byte 63 | Port uint 64 | } 65 | 66 | // Client is vmess connection generator 67 | type Client struct { 68 | user []*ID 69 | uuid *uuid.UUID 70 | security Security 71 | } 72 | 73 | // Config of vmess 74 | type Config struct { 75 | UUID string 76 | AlterID uint16 77 | Security string 78 | Port string 79 | HostName string 80 | } 81 | 82 | // StreamConn return a Conn with net.Conn and DstAddr 83 | func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { 84 | r := rand.Intn(len(c.user)) 85 | return newConn(conn, c.user[r], dst, c.security) 86 | } 87 | 88 | // NewClient return Client instance 89 | func NewClient(config Config) (*Client, error) { 90 | uid, err := uuid.FromString(config.UUID) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | var security Security 96 | switch config.Security { 97 | case "aes-128-gcm": 98 | security = SecurityAES128GCM 99 | case "chacha20-poly1305": 100 | security = SecurityCHACHA20POLY1305 101 | case "none": 102 | security = SecurityNone 103 | case "auto": 104 | security = SecurityCHACHA20POLY1305 105 | if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { 106 | security = SecurityAES128GCM 107 | } 108 | default: 109 | return nil, fmt.Errorf("Unknown security type: %s", config.Security) 110 | } 111 | 112 | return &Client{ 113 | user: newAlterIDs(newID(&uid), config.AlterID), 114 | uuid: &uid, 115 | security: security, 116 | }, nil 117 | } 118 | -------------------------------------------------------------------------------- /component/vmess/aead.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/cipher" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | 9 | "github.com/Dreamacro/clash/common/pool" 10 | ) 11 | 12 | type aeadWriter struct { 13 | io.Writer 14 | cipher.AEAD 15 | nonce [32]byte 16 | count uint16 17 | iv []byte 18 | } 19 | 20 | func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter { 21 | return &aeadWriter{Writer: w, AEAD: aead, iv: iv} 22 | } 23 | 24 | func (w *aeadWriter) Write(b []byte) (n int, err error) { 25 | buf := pool.Get(pool.RelayBufferSize) 26 | defer pool.Put(buf) 27 | length := len(b) 28 | for { 29 | if length == 0 { 30 | break 31 | } 32 | readLen := chunkSize - w.Overhead() 33 | if length < readLen { 34 | readLen = length 35 | } 36 | payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()] 37 | copy(payloadBuf, b[n:n+readLen]) 38 | 39 | binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen+w.Overhead())) 40 | binary.BigEndian.PutUint16(w.nonce[:2], w.count) 41 | copy(w.nonce[2:], w.iv[2:12]) 42 | 43 | w.Seal(payloadBuf[:0], w.nonce[:w.NonceSize()], payloadBuf[:readLen], nil) 44 | w.count++ 45 | 46 | _, err = w.Writer.Write(buf[:lenSize+readLen+w.Overhead()]) 47 | if err != nil { 48 | break 49 | } 50 | n += readLen 51 | length -= readLen 52 | } 53 | return 54 | } 55 | 56 | type aeadReader struct { 57 | io.Reader 58 | cipher.AEAD 59 | nonce [32]byte 60 | buf []byte 61 | offset int 62 | iv []byte 63 | sizeBuf []byte 64 | count uint16 65 | } 66 | 67 | func newAEADReader(r io.Reader, aead cipher.AEAD, iv []byte) *aeadReader { 68 | return &aeadReader{Reader: r, AEAD: aead, iv: iv, sizeBuf: make([]byte, lenSize)} 69 | } 70 | 71 | func (r *aeadReader) Read(b []byte) (int, error) { 72 | if r.buf != nil { 73 | n := copy(b, r.buf[r.offset:]) 74 | r.offset += n 75 | if r.offset == len(r.buf) { 76 | pool.Put(r.buf) 77 | r.buf = nil 78 | } 79 | return n, nil 80 | } 81 | 82 | _, err := io.ReadFull(r.Reader, r.sizeBuf) 83 | if err != nil { 84 | return 0, err 85 | } 86 | 87 | size := int(binary.BigEndian.Uint16(r.sizeBuf)) 88 | if size > maxSize { 89 | return 0, errors.New("Buffer is larger than standard") 90 | } 91 | 92 | buf := pool.Get(size) 93 | _, err = io.ReadFull(r.Reader, buf[:size]) 94 | if err != nil { 95 | pool.Put(buf) 96 | return 0, err 97 | } 98 | 99 | binary.BigEndian.PutUint16(r.nonce[:2], r.count) 100 | copy(r.nonce[2:], r.iv[2:12]) 101 | 102 | _, err = r.Open(buf[:0], r.nonce[:r.NonceSize()], buf[:size], nil) 103 | r.count++ 104 | if err != nil { 105 | return 0, err 106 | } 107 | realLen := size - r.Overhead() 108 | n := copy(b, buf[:realLen]) 109 | if len(b) >= realLen { 110 | pool.Put(buf) 111 | return n, nil 112 | } 113 | 114 | r.offset = n 115 | r.buf = buf[:realLen] 116 | return n, nil 117 | } 118 | -------------------------------------------------------------------------------- /proxy/http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base64" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | adapters "github.com/Dreamacro/clash/adapters/inbound" 12 | "github.com/Dreamacro/clash/common/cache" 13 | "github.com/Dreamacro/clash/component/auth" 14 | "github.com/Dreamacro/clash/log" 15 | authStore "github.com/Dreamacro/clash/proxy/auth" 16 | "github.com/Dreamacro/clash/tunnel" 17 | ) 18 | 19 | type HttpListener struct { 20 | net.Listener 21 | address string 22 | closed bool 23 | cache *cache.Cache 24 | } 25 | 26 | func NewHttpProxy(addr string) (*HttpListener, error) { 27 | l, err := net.Listen("tcp", addr) 28 | if err != nil { 29 | return nil, err 30 | } 31 | hl := &HttpListener{l, addr, false, cache.New(30 * time.Second)} 32 | 33 | go func() { 34 | log.Infoln("HTTP proxy listening at: %s", addr) 35 | 36 | for { 37 | c, err := hl.Accept() 38 | if err != nil { 39 | if hl.closed { 40 | break 41 | } 42 | continue 43 | } 44 | go HandleConn(c, hl.cache) 45 | } 46 | }() 47 | 48 | return hl, nil 49 | } 50 | 51 | func (l *HttpListener) Close() { 52 | l.closed = true 53 | l.Listener.Close() 54 | } 55 | 56 | func (l *HttpListener) Address() string { 57 | return l.address 58 | } 59 | 60 | func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) { 61 | if result := cache.Get(loginStr); result != nil { 62 | ret = result.(bool) 63 | } 64 | loginData, err := base64.StdEncoding.DecodeString(loginStr) 65 | login := strings.Split(string(loginData), ":") 66 | ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1]) 67 | 68 | cache.Put(loginStr, ret, time.Minute) 69 | return 70 | } 71 | 72 | func HandleConn(conn net.Conn, cache *cache.Cache) { 73 | br := bufio.NewReader(conn) 74 | request, err := http.ReadRequest(br) 75 | if err != nil || request.URL.Host == "" { 76 | conn.Close() 77 | return 78 | } 79 | 80 | authenticator := authStore.Authenticator() 81 | if authenticator != nil { 82 | if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { 83 | _, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) 84 | conn.Close() 85 | return 86 | } else if !canActivate(authStrings[1], authenticator, cache) { 87 | conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) 88 | log.Infoln("Auth failed from %s", conn.RemoteAddr().String()) 89 | conn.Close() 90 | return 91 | } 92 | } 93 | 94 | if request.Method == http.MethodConnect { 95 | _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) 96 | if err != nil { 97 | return 98 | } 99 | tunnel.Add(adapters.NewHTTPS(request, conn)) 100 | return 101 | } 102 | 103 | tunnel.Add(adapters.NewHTTP(request, conn)) 104 | } 105 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=clash 2 | BINDIR=bin 3 | VERSION=$(shell git describe --tags || echo "unknown version") 4 | BUILDTIME=$(shell date -u) 5 | GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ 6 | -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ 7 | -w -s' 8 | 9 | PLATFORM_LIST = \ 10 | darwin-amd64 \ 11 | linux-386 \ 12 | linux-amd64 \ 13 | linux-armv5 \ 14 | linux-armv6 \ 15 | linux-armv7 \ 16 | linux-armv8 \ 17 | linux-mips-softfloat \ 18 | linux-mips-hardfloat \ 19 | linux-mipsle-softfloat \ 20 | linux-mipsle-hardfloat \ 21 | linux-mips64 \ 22 | linux-mips64le \ 23 | freebsd-386 \ 24 | freebsd-amd64 25 | 26 | WINDOWS_ARCH_LIST = \ 27 | windows-386 \ 28 | windows-amd64 29 | 30 | all: linux-amd64 darwin-amd64 windows-amd64 # Most used 31 | 32 | docker: 33 | $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 34 | 35 | darwin-amd64: 36 | GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 37 | 38 | linux-386: 39 | GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 40 | 41 | linux-amd64: 42 | GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 43 | 44 | linux-armv5: 45 | GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 46 | 47 | linux-armv6: 48 | GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 49 | 50 | linux-armv7: 51 | GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 52 | 53 | linux-armv8: 54 | GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 55 | 56 | linux-mips-softfloat: 57 | GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 58 | 59 | linux-mips-hardfloat: 60 | GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 61 | 62 | linux-mipsle-softfloat: 63 | GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 64 | 65 | linux-mipsle-hardfloat: 66 | GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 67 | 68 | linux-mips64: 69 | GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 70 | 71 | linux-mips64le: 72 | GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 73 | 74 | freebsd-386: 75 | GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 76 | 77 | freebsd-amd64: 78 | GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 79 | 80 | windows-386: 81 | GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe 82 | 83 | windows-amd64: 84 | GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe 85 | 86 | gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) 87 | zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) 88 | 89 | $(gz_releases): %.gz : % 90 | chmod +x $(BINDIR)/$(NAME)-$(basename $@) 91 | gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@) 92 | 93 | $(zip_releases): %.zip : % 94 | zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe 95 | 96 | all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) 97 | 98 | releases: $(gz_releases) $(zip_releases) 99 | clean: 100 | rm $(BINDIR)/* 101 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 33 | 34 | ------------------------------------------------------------------ 35 | 36 | 40 | 41 | ### Clash config 42 | 46 |
47 | config.yaml 48 | 49 | ```yaml 50 | …… 51 | ``` 52 | 53 |
54 | 55 | ### Clash log 56 | 60 | ``` 61 | …… 62 | ``` 63 | 64 | ### 环境 Environment 65 | 66 | * 操作系统 (the OS that the Clash core is running on) 67 | …… 68 | * 网路环境或拓扑 (network conditions/topology) 69 | …… 70 | * iptables,如果适用 (if applicable) 71 | …… 72 | * ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) 73 | …… 74 | * 其他 (any other information that would be useful) 75 | …… 76 | 77 | ### 说明 Description 78 | 79 | 82 | 83 | ### 重现问题的具体布骤 Steps to Reproduce 84 | 85 | 1. [First Step] 86 | 2. [Second Step] 87 | 3. …… 88 | 89 | **我预期会发生……?** 90 | 91 | 92 | **实际上发生了什么?** 93 | 94 | 95 | ### 可能的解决方案 Possible Solution 96 | 97 | 98 | 99 | 100 | ### 更多信息 More Information 101 | -------------------------------------------------------------------------------- /common/structure/structure_test.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | var decoder = NewDecoder(Option{TagName: "test"}) 9 | var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) 10 | 11 | type Baz struct { 12 | Foo int `test:"foo"` 13 | Bar string `test:"bar"` 14 | } 15 | 16 | type BazSlice struct { 17 | Foo int `test:"foo"` 18 | Bar []string `test:"bar"` 19 | } 20 | 21 | type BazOptional struct { 22 | Foo int `test:"foo,omitempty"` 23 | Bar string `test:"bar,omitempty"` 24 | } 25 | 26 | func TestStructure_Basic(t *testing.T) { 27 | rawMap := map[string]interface{}{ 28 | "foo": 1, 29 | "bar": "test", 30 | "extra": false, 31 | } 32 | 33 | goal := &Baz{ 34 | Foo: 1, 35 | Bar: "test", 36 | } 37 | 38 | s := &Baz{} 39 | err := decoder.Decode(rawMap, s) 40 | if err != nil { 41 | t.Fatal(err.Error()) 42 | } 43 | if !reflect.DeepEqual(s, goal) { 44 | t.Fatalf("bad: %#v", s) 45 | } 46 | } 47 | 48 | func TestStructure_Slice(t *testing.T) { 49 | rawMap := map[string]interface{}{ 50 | "foo": 1, 51 | "bar": []string{"one", "two"}, 52 | } 53 | 54 | goal := &BazSlice{ 55 | Foo: 1, 56 | Bar: []string{"one", "two"}, 57 | } 58 | 59 | s := &BazSlice{} 60 | err := decoder.Decode(rawMap, s) 61 | if err != nil { 62 | t.Fatal(err.Error()) 63 | } 64 | if !reflect.DeepEqual(s, goal) { 65 | t.Fatalf("bad: %#v", s) 66 | } 67 | } 68 | 69 | func TestStructure_Optional(t *testing.T) { 70 | rawMap := map[string]interface{}{ 71 | "foo": 1, 72 | } 73 | 74 | goal := &BazOptional{ 75 | Foo: 1, 76 | } 77 | 78 | s := &BazOptional{} 79 | err := decoder.Decode(rawMap, s) 80 | if err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | if !reflect.DeepEqual(s, goal) { 84 | t.Fatalf("bad: %#v", s) 85 | } 86 | } 87 | 88 | func TestStructure_MissingKey(t *testing.T) { 89 | rawMap := map[string]interface{}{ 90 | "foo": 1, 91 | } 92 | 93 | s := &Baz{} 94 | err := decoder.Decode(rawMap, s) 95 | if err == nil { 96 | t.Fatalf("should throw error: %#v", s) 97 | } 98 | } 99 | 100 | func TestStructure_ParamError(t *testing.T) { 101 | rawMap := map[string]interface{}{} 102 | s := Baz{} 103 | err := decoder.Decode(rawMap, s) 104 | if err == nil { 105 | t.Fatalf("should throw error: %#v", s) 106 | } 107 | } 108 | 109 | func TestStructure_SliceTypeError(t *testing.T) { 110 | rawMap := map[string]interface{}{ 111 | "foo": 1, 112 | "bar": []int{1, 2}, 113 | } 114 | 115 | s := &BazSlice{} 116 | err := decoder.Decode(rawMap, s) 117 | if err == nil { 118 | t.Fatalf("should throw error: %#v", s) 119 | } 120 | } 121 | 122 | func TestStructure_WeakType(t *testing.T) { 123 | rawMap := map[string]interface{}{ 124 | "foo": "1", 125 | "bar": []int{1}, 126 | } 127 | 128 | goal := &BazSlice{ 129 | Foo: 1, 130 | Bar: []string{"1"}, 131 | } 132 | 133 | s := &BazSlice{} 134 | err := weakTypeDecoder.Decode(rawMap, s) 135 | if err != nil { 136 | t.Fatal(err.Error()) 137 | } 138 | if !reflect.DeepEqual(s, goal) { 139 | t.Fatalf("bad: %#v", s) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /component/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/component/trie" 9 | ) 10 | 11 | var ( 12 | // DefaultResolver aim to resolve ip 13 | DefaultResolver Resolver 14 | 15 | // DisableIPv6 means don't resolve ipv6 host 16 | // default value is true 17 | DisableIPv6 = true 18 | 19 | // DefaultHosts aim to resolve hosts 20 | DefaultHosts = trie.New() 21 | ) 22 | 23 | var ( 24 | ErrIPNotFound = errors.New("couldn't find ip") 25 | ErrIPVersion = errors.New("ip version error") 26 | ErrIPv6Disabled = errors.New("ipv6 disabled") 27 | ) 28 | 29 | type Resolver interface { 30 | ResolveIP(host string) (ip net.IP, err error) 31 | ResolveIPv4(host string) (ip net.IP, err error) 32 | ResolveIPv6(host string) (ip net.IP, err error) 33 | } 34 | 35 | // ResolveIPv4 with a host, return ipv4 36 | func ResolveIPv4(host string) (net.IP, error) { 37 | if node := DefaultHosts.Search(host); node != nil { 38 | if ip := node.Data.(net.IP).To4(); ip != nil { 39 | return ip, nil 40 | } 41 | } 42 | 43 | ip := net.ParseIP(host) 44 | if ip != nil { 45 | if !strings.Contains(host, ":") { 46 | return ip, nil 47 | } 48 | return nil, ErrIPVersion 49 | } 50 | 51 | if DefaultResolver != nil { 52 | return DefaultResolver.ResolveIPv4(host) 53 | } 54 | 55 | ipAddrs, err := net.LookupIP(host) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | for _, ip := range ipAddrs { 61 | if ip4 := ip.To4(); ip4 != nil { 62 | return ip4, nil 63 | } 64 | } 65 | 66 | return nil, ErrIPNotFound 67 | } 68 | 69 | // ResolveIPv6 with a host, return ipv6 70 | func ResolveIPv6(host string) (net.IP, error) { 71 | if DisableIPv6 { 72 | return nil, ErrIPv6Disabled 73 | } 74 | 75 | if node := DefaultHosts.Search(host); node != nil { 76 | if ip := node.Data.(net.IP).To16(); ip != nil { 77 | return ip, nil 78 | } 79 | } 80 | 81 | ip := net.ParseIP(host) 82 | if ip != nil { 83 | if strings.Contains(host, ":") { 84 | return ip, nil 85 | } 86 | return nil, ErrIPVersion 87 | } 88 | 89 | if DefaultResolver != nil { 90 | return DefaultResolver.ResolveIPv6(host) 91 | } 92 | 93 | ipAddrs, err := net.LookupIP(host) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | for _, ip := range ipAddrs { 99 | if ip.To4() == nil { 100 | return ip, nil 101 | } 102 | } 103 | 104 | return nil, ErrIPNotFound 105 | } 106 | 107 | // ResolveIP with a host, return ip 108 | func ResolveIP(host string) (net.IP, error) { 109 | if node := DefaultHosts.Search(host); node != nil { 110 | return node.Data.(net.IP), nil 111 | } 112 | 113 | if DefaultResolver != nil { 114 | if DisableIPv6 { 115 | return DefaultResolver.ResolveIPv4(host) 116 | } 117 | return DefaultResolver.ResolveIP(host) 118 | } else if DisableIPv6 { 119 | return ResolveIPv4(host) 120 | } 121 | 122 | ip := net.ParseIP(host) 123 | if ip != nil { 124 | return ip, nil 125 | } 126 | 127 | ipAddr, err := net.ResolveIPAddr("ip", host) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return ipAddr.IP, nil 133 | } 134 | -------------------------------------------------------------------------------- /adapters/outbound/trojan.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "strconv" 9 | 10 | "github.com/Dreamacro/clash/component/dialer" 11 | "github.com/Dreamacro/clash/component/trojan" 12 | C "github.com/Dreamacro/clash/constant" 13 | ) 14 | 15 | type Trojan struct { 16 | *Base 17 | instance *trojan.Trojan 18 | } 19 | 20 | type TrojanOption struct { 21 | Name string `proxy:"name"` 22 | Server string `proxy:"server"` 23 | Port int `proxy:"port"` 24 | Password string `proxy:"password"` 25 | ALPN []string `proxy:"alpn,omitempty"` 26 | SNI string `proxy:"sni,omitempty"` 27 | SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 28 | UDP bool `proxy:"udp,omitempty"` 29 | } 30 | 31 | func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 32 | c, err := t.instance.StreamConn(c) 33 | if err != nil { 34 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 35 | } 36 | 37 | err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) 38 | return c, err 39 | } 40 | 41 | func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 42 | c, err := dialer.DialContext(ctx, "tcp", t.addr) 43 | if err != nil { 44 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 45 | } 46 | tcpKeepAlive(c) 47 | c, err = t.StreamConn(c, metadata) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return NewConn(c, t), err 53 | } 54 | 55 | func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 56 | ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 57 | defer cancel() 58 | c, err := dialer.DialContext(ctx, "tcp", t.addr) 59 | if err != nil { 60 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 61 | } 62 | tcpKeepAlive(c) 63 | c, err = t.instance.StreamConn(c) 64 | if err != nil { 65 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 66 | } 67 | 68 | err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | pc := t.instance.PacketConn(c) 74 | return newPacketConn(pc, t), err 75 | } 76 | 77 | func (t *Trojan) MarshalJSON() ([]byte, error) { 78 | return json.Marshal(map[string]string{ 79 | "type": t.Type().String(), 80 | }) 81 | } 82 | 83 | func NewTrojan(option TrojanOption) (*Trojan, error) { 84 | addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 85 | 86 | tOption := &trojan.Option{ 87 | Password: option.Password, 88 | ALPN: option.ALPN, 89 | ServerName: option.Server, 90 | SkipCertVerify: option.SkipCertVerify, 91 | ClientSessionCache: getClientSessionCache(), 92 | } 93 | 94 | if option.SNI != "" { 95 | tOption.ServerName = option.SNI 96 | } 97 | 98 | return &Trojan{ 99 | Base: &Base{ 100 | name: option.Name, 101 | addr: addr, 102 | tp: C.Trojan, 103 | udp: option.UDP, 104 | }, 105 | instance: trojan.New(tOption), 106 | }, nil 107 | } 108 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "path/filepath" 9 | "runtime" 10 | "syscall" 11 | 12 | "github.com/Dreamacro/clash/config" 13 | "github.com/Dreamacro/clash/constant" 14 | C "github.com/Dreamacro/clash/constant" 15 | "github.com/Dreamacro/clash/hub" 16 | "github.com/Dreamacro/clash/hub/executor" 17 | "github.com/Dreamacro/clash/log" 18 | ) 19 | 20 | var ( 21 | flagset map[string]bool 22 | version bool 23 | testConfig bool 24 | homeDir string 25 | configFile string 26 | externalUI string 27 | externalController string 28 | secret string 29 | ) 30 | 31 | func init() { 32 | flag.StringVar(&homeDir, "d", "", "set configuration directory") 33 | flag.StringVar(&configFile, "f", "", "specify configuration file") 34 | flag.StringVar(&externalUI, "ext-ui", "", "override external ui directory") 35 | flag.StringVar(&externalController, "ext-ctl", "", "override external controller address") 36 | flag.StringVar(&secret, "secret", "", "override secret for RESTful API") 37 | flag.BoolVar(&version, "v", false, "show current version of clash") 38 | flag.BoolVar(&testConfig, "t", false, "test configuration and exit") 39 | flag.Parse() 40 | 41 | flagset = map[string]bool{} 42 | flag.Visit(func(f *flag.Flag) { 43 | flagset[f.Name] = true 44 | }) 45 | } 46 | 47 | func main() { 48 | if version { 49 | fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime) 50 | return 51 | } 52 | 53 | if homeDir != "" { 54 | if !filepath.IsAbs(homeDir) { 55 | currentDir, _ := os.Getwd() 56 | homeDir = filepath.Join(currentDir, homeDir) 57 | } 58 | C.SetHomeDir(homeDir) 59 | } 60 | 61 | if configFile != "" { 62 | if !filepath.IsAbs(configFile) { 63 | currentDir, _ := os.Getwd() 64 | configFile = filepath.Join(currentDir, configFile) 65 | } 66 | C.SetConfig(configFile) 67 | } else { 68 | configFile := filepath.Join(C.Path.HomeDir(), C.Path.Config()) 69 | C.SetConfig(configFile) 70 | } 71 | 72 | if err := config.Init(C.Path.HomeDir()); err != nil { 73 | log.Fatalln("Initial configuration directory error: %s", err.Error()) 74 | } 75 | 76 | if testConfig { 77 | if _, err := executor.Parse(); err != nil { 78 | log.Errorln(err.Error()) 79 | fmt.Printf("configuration file %s test failed\n", constant.Path.Config()) 80 | os.Exit(1) 81 | } 82 | fmt.Printf("configuration file %s test is successful\n", constant.Path.Config()) 83 | return 84 | } 85 | 86 | var options []hub.Option 87 | if flagset["ext-ui"] { 88 | options = append(options, hub.WithExternalUI(externalUI)) 89 | } 90 | if flagset["ext-ctl"] { 91 | options = append(options, hub.WithExternalController(externalController)) 92 | } 93 | if flagset["secret"] { 94 | options = append(options, hub.WithSecret(secret)) 95 | } 96 | 97 | if err := hub.Parse(options...); err != nil { 98 | log.Fatalln("Parse config error: %s", err.Error()) 99 | } 100 | 101 | sigCh := make(chan os.Signal, 1) 102 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 103 | <-sigCh 104 | } 105 | -------------------------------------------------------------------------------- /component/trie/domain.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | wildcard = "*" 10 | dotWildcard = "" 11 | complexWildcard = "+" 12 | domainStep = "." 13 | ) 14 | 15 | var ( 16 | // ErrInvalidDomain means insert domain is invalid 17 | ErrInvalidDomain = errors.New("invalid domain") 18 | ) 19 | 20 | // DomainTrie contains the main logic for adding and searching nodes for domain segments. 21 | // support wildcard domain (e.g *.google.com) 22 | type DomainTrie struct { 23 | root *Node 24 | } 25 | 26 | func validAndSplitDomain(domain string) ([]string, bool) { 27 | if domain != "" && domain[len(domain)-1] == '.' { 28 | return nil, false 29 | } 30 | 31 | parts := strings.Split(domain, domainStep) 32 | if len(parts) == 1 { 33 | if parts[0] == "" { 34 | return nil, false 35 | } 36 | 37 | return parts, true 38 | } 39 | 40 | for _, part := range parts[1:] { 41 | if part == "" { 42 | return nil, false 43 | } 44 | } 45 | 46 | return parts, true 47 | } 48 | 49 | // Insert adds a node to the trie. 50 | // Support 51 | // 1. www.example.com 52 | // 2. *.example.com 53 | // 3. subdomain.*.example.com 54 | // 4. .example.com 55 | // 5. +.example.com 56 | func (t *DomainTrie) Insert(domain string, data interface{}) error { 57 | parts, valid := validAndSplitDomain(domain) 58 | if !valid { 59 | return ErrInvalidDomain 60 | } 61 | 62 | if parts[0] == complexWildcard { 63 | t.insert(parts[1:], data) 64 | parts[0] = dotWildcard 65 | t.insert(parts, data) 66 | } else { 67 | t.insert(parts, data) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (t *DomainTrie) insert(parts []string, data interface{}) { 74 | node := t.root 75 | // reverse storage domain part to save space 76 | for i := len(parts) - 1; i >= 0; i-- { 77 | part := parts[i] 78 | if !node.hasChild(part) { 79 | node.addChild(part, newNode(nil)) 80 | } 81 | 82 | node = node.getChild(part) 83 | } 84 | 85 | node.Data = data 86 | } 87 | 88 | // Search is the most important part of the Trie. 89 | // Priority as: 90 | // 1. static part 91 | // 2. wildcard domain 92 | // 2. dot wildcard domain 93 | func (t *DomainTrie) Search(domain string) *Node { 94 | parts, valid := validAndSplitDomain(domain) 95 | if !valid || parts[0] == "" { 96 | return nil 97 | } 98 | 99 | n := t.search(t.root, parts) 100 | 101 | if n == nil || n.Data == nil { 102 | return nil 103 | } 104 | 105 | return n 106 | } 107 | 108 | func (t *DomainTrie) search(node *Node, parts []string) *Node { 109 | if len(parts) == 0 { 110 | return node 111 | } 112 | 113 | if c := node.getChild(parts[len(parts)-1]); c != nil { 114 | if n := t.search(c, parts[:len(parts)-1]); n != nil { 115 | return n 116 | } 117 | } 118 | 119 | if c := node.getChild(wildcard); c != nil { 120 | if n := t.search(c, parts[:len(parts)-1]); n != nil { 121 | return n 122 | } 123 | } 124 | 125 | if c := node.getChild(dotWildcard); c != nil { 126 | return c 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // New returns a new, empty Trie. 133 | func New() *DomainTrie { 134 | return &DomainTrie{root: newNode(nil)} 135 | } 136 | -------------------------------------------------------------------------------- /adapters/outboundgroup/loadbalance.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net" 7 | 8 | "github.com/Dreamacro/clash/adapters/outbound" 9 | "github.com/Dreamacro/clash/adapters/provider" 10 | "github.com/Dreamacro/clash/common/murmur3" 11 | "github.com/Dreamacro/clash/common/singledo" 12 | C "github.com/Dreamacro/clash/constant" 13 | 14 | "golang.org/x/net/publicsuffix" 15 | ) 16 | 17 | type LoadBalance struct { 18 | *outbound.Base 19 | single *singledo.Single 20 | maxRetry int 21 | providers []provider.ProxyProvider 22 | } 23 | 24 | func getKey(metadata *C.Metadata) string { 25 | if metadata.Host != "" { 26 | // ip host 27 | if ip := net.ParseIP(metadata.Host); ip != nil { 28 | return metadata.Host 29 | } 30 | 31 | if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil { 32 | return etld 33 | } 34 | } 35 | 36 | if metadata.DstIP == nil { 37 | return "" 38 | } 39 | 40 | return metadata.DstIP.String() 41 | } 42 | 43 | func jumpHash(key uint64, buckets int32) int32 { 44 | var b, j int64 45 | 46 | for j < int64(buckets) { 47 | b = j 48 | key = key*2862933555777941757 + 1 49 | j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) 50 | } 51 | 52 | return int32(b) 53 | } 54 | 55 | func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { 56 | defer func() { 57 | if err == nil { 58 | c.AppendToChains(lb) 59 | } 60 | }() 61 | 62 | proxy := lb.Unwrap(metadata) 63 | 64 | c, err = proxy.DialContext(ctx, metadata) 65 | return 66 | } 67 | 68 | func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { 69 | defer func() { 70 | if err == nil { 71 | pc.AppendToChains(lb) 72 | } 73 | }() 74 | 75 | proxy := lb.Unwrap(metadata) 76 | 77 | return proxy.DialUDP(metadata) 78 | } 79 | 80 | func (lb *LoadBalance) SupportUDP() bool { 81 | return true 82 | } 83 | 84 | func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { 85 | key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) 86 | proxies := lb.proxies() 87 | buckets := int32(len(proxies)) 88 | for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { 89 | idx := jumpHash(key, buckets) 90 | proxy := proxies[idx] 91 | if proxy.Alive() { 92 | return proxy 93 | } 94 | } 95 | 96 | return proxies[0] 97 | } 98 | 99 | func (lb *LoadBalance) proxies() []C.Proxy { 100 | elm, _, _ := lb.single.Do(func() (interface{}, error) { 101 | return getProvidersProxies(lb.providers), nil 102 | }) 103 | 104 | return elm.([]C.Proxy) 105 | } 106 | 107 | func (lb *LoadBalance) MarshalJSON() ([]byte, error) { 108 | var all []string 109 | for _, proxy := range lb.proxies() { 110 | all = append(all, proxy.Name()) 111 | } 112 | return json.Marshal(map[string]interface{}{ 113 | "type": lb.Type().String(), 114 | "all": all, 115 | }) 116 | } 117 | 118 | func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { 119 | return &LoadBalance{ 120 | Base: outbound.NewBase(name, "", C.LoadBalance, false), 121 | single: singledo.NewSingle(defaultGetProxiesDuration), 122 | maxRetry: 3, 123 | providers: providers, 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /common/murmur3/murmur32.go: -------------------------------------------------------------------------------- 1 | package murmur3 2 | 3 | // https://github.com/spaolacci/murmur3/blob/master/murmur32.go 4 | 5 | import ( 6 | "hash" 7 | "math/bits" 8 | "unsafe" 9 | ) 10 | 11 | // Make sure interfaces are correctly implemented. 12 | var ( 13 | _ hash.Hash32 = new(digest32) 14 | _ bmixer = new(digest32) 15 | ) 16 | 17 | const ( 18 | c1_32 uint32 = 0xcc9e2d51 19 | c2_32 uint32 = 0x1b873593 20 | ) 21 | 22 | // digest32 represents a partial evaluation of a 32 bites hash. 23 | type digest32 struct { 24 | digest 25 | h1 uint32 // Unfinalized running hash. 26 | } 27 | 28 | // New32 returns new 32-bit hasher 29 | func New32() hash.Hash32 { return New32WithSeed(0) } 30 | 31 | // New32WithSeed returns new 32-bit hasher set with explicit seed value 32 | func New32WithSeed(seed uint32) hash.Hash32 { 33 | d := new(digest32) 34 | d.seed = seed 35 | d.bmixer = d 36 | d.Reset() 37 | return d 38 | } 39 | 40 | func (d *digest32) Size() int { return 4 } 41 | 42 | func (d *digest32) reset() { d.h1 = d.seed } 43 | 44 | func (d *digest32) Sum(b []byte) []byte { 45 | h := d.Sum32() 46 | return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h)) 47 | } 48 | 49 | // Digest as many blocks as possible. 50 | func (d *digest32) bmix(p []byte) (tail []byte) { 51 | h1 := d.h1 52 | 53 | nblocks := len(p) / 4 54 | for i := 0; i < nblocks; i++ { 55 | k1 := *(*uint32)(unsafe.Pointer(&p[i*4])) 56 | 57 | k1 *= c1_32 58 | k1 = bits.RotateLeft32(k1, 15) 59 | k1 *= c2_32 60 | 61 | h1 ^= k1 62 | h1 = bits.RotateLeft32(h1, 13) 63 | h1 = h1*4 + h1 + 0xe6546b64 64 | } 65 | d.h1 = h1 66 | return p[nblocks*d.Size():] 67 | } 68 | 69 | func (d *digest32) Sum32() (h1 uint32) { 70 | 71 | h1 = d.h1 72 | 73 | var k1 uint32 74 | switch len(d.tail) & 3 { 75 | case 3: 76 | k1 ^= uint32(d.tail[2]) << 16 77 | fallthrough 78 | case 2: 79 | k1 ^= uint32(d.tail[1]) << 8 80 | fallthrough 81 | case 1: 82 | k1 ^= uint32(d.tail[0]) 83 | k1 *= c1_32 84 | k1 = bits.RotateLeft32(k1, 15) 85 | k1 *= c2_32 86 | h1 ^= k1 87 | } 88 | 89 | h1 ^= uint32(d.clen) 90 | 91 | h1 ^= h1 >> 16 92 | h1 *= 0x85ebca6b 93 | h1 ^= h1 >> 13 94 | h1 *= 0xc2b2ae35 95 | h1 ^= h1 >> 16 96 | 97 | return h1 98 | } 99 | 100 | func Sum32(data []byte) uint32 { return Sum32WithSeed(data, 0) } 101 | 102 | func Sum32WithSeed(data []byte, seed uint32) uint32 { 103 | h1 := seed 104 | 105 | nblocks := len(data) / 4 106 | for i := 0; i < nblocks; i++ { 107 | k1 := *(*uint32)(unsafe.Pointer(&data[i*4])) 108 | 109 | k1 *= c1_32 110 | k1 = bits.RotateLeft32(k1, 15) 111 | k1 *= c2_32 112 | 113 | h1 ^= k1 114 | h1 = bits.RotateLeft32(h1, 13) 115 | h1 = h1*4 + h1 + 0xe6546b64 116 | } 117 | 118 | tail := data[nblocks*4:] 119 | 120 | var k1 uint32 121 | switch len(tail) & 3 { 122 | case 3: 123 | k1 ^= uint32(tail[2]) << 16 124 | fallthrough 125 | case 2: 126 | k1 ^= uint32(tail[1]) << 8 127 | fallthrough 128 | case 1: 129 | k1 ^= uint32(tail[0]) 130 | k1 *= c1_32 131 | k1 = bits.RotateLeft32(k1, 15) 132 | k1 *= c2_32 133 | h1 ^= k1 134 | } 135 | 136 | h1 ^= uint32(len(data)) 137 | 138 | h1 ^= h1 >> 16 139 | h1 *= 0x85ebca6b 140 | h1 ^= h1 >> 13 141 | h1 *= 0xc2b2ae35 142 | h1 ^= h1 >> 16 143 | 144 | return h1 145 | } 146 | -------------------------------------------------------------------------------- /component/fakeip/pool.go: -------------------------------------------------------------------------------- 1 | package fakeip 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | 8 | "github.com/Dreamacro/clash/common/cache" 9 | "github.com/Dreamacro/clash/component/trie" 10 | ) 11 | 12 | // Pool is a implementation about fake ip generator without storage 13 | type Pool struct { 14 | max uint32 15 | min uint32 16 | gateway uint32 17 | offset uint32 18 | mux sync.Mutex 19 | host *trie.DomainTrie 20 | cache *cache.LruCache 21 | } 22 | 23 | // Lookup return a fake ip with host 24 | func (p *Pool) Lookup(host string) net.IP { 25 | p.mux.Lock() 26 | defer p.mux.Unlock() 27 | if elm, exist := p.cache.Get(host); exist { 28 | ip := elm.(net.IP) 29 | 30 | // ensure ip --> host on head of linked list 31 | n := ipToUint(ip.To4()) 32 | offset := n - p.min + 1 33 | p.cache.Get(offset) 34 | return ip 35 | } 36 | 37 | ip := p.get(host) 38 | p.cache.Set(host, ip) 39 | return ip 40 | } 41 | 42 | // LookBack return host with the fake ip 43 | func (p *Pool) LookBack(ip net.IP) (string, bool) { 44 | p.mux.Lock() 45 | defer p.mux.Unlock() 46 | 47 | if ip = ip.To4(); ip == nil { 48 | return "", false 49 | } 50 | 51 | n := ipToUint(ip.To4()) 52 | offset := n - p.min + 1 53 | 54 | if elm, exist := p.cache.Get(offset); exist { 55 | host := elm.(string) 56 | 57 | // ensure host --> ip on head of linked list 58 | p.cache.Get(host) 59 | return host, true 60 | } 61 | 62 | return "", false 63 | } 64 | 65 | // LookupHost return if domain in host 66 | func (p *Pool) LookupHost(domain string) bool { 67 | if p.host == nil { 68 | return false 69 | } 70 | return p.host.Search(domain) != nil 71 | } 72 | 73 | // Exist returns if given ip exists in fake-ip pool 74 | func (p *Pool) Exist(ip net.IP) bool { 75 | p.mux.Lock() 76 | defer p.mux.Unlock() 77 | 78 | if ip = ip.To4(); ip == nil { 79 | return false 80 | } 81 | 82 | n := ipToUint(ip.To4()) 83 | offset := n - p.min + 1 84 | return p.cache.Exist(offset) 85 | } 86 | 87 | // Gateway return gateway ip 88 | func (p *Pool) Gateway() net.IP { 89 | return uintToIP(p.gateway) 90 | } 91 | 92 | func (p *Pool) get(host string) net.IP { 93 | current := p.offset 94 | for { 95 | p.offset = (p.offset + 1) % (p.max - p.min) 96 | // Avoid infinite loops 97 | if p.offset == current { 98 | break 99 | } 100 | 101 | if !p.cache.Exist(p.offset) { 102 | break 103 | } 104 | } 105 | ip := uintToIP(p.min + p.offset - 1) 106 | p.cache.Set(p.offset, host) 107 | return ip 108 | } 109 | 110 | func ipToUint(ip net.IP) uint32 { 111 | v := uint32(ip[0]) << 24 112 | v += uint32(ip[1]) << 16 113 | v += uint32(ip[2]) << 8 114 | v += uint32(ip[3]) 115 | return v 116 | } 117 | 118 | func uintToIP(v uint32) net.IP { 119 | return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 120 | } 121 | 122 | // New return Pool instance 123 | func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { 124 | min := ipToUint(ipnet.IP) + 2 125 | 126 | ones, bits := ipnet.Mask.Size() 127 | total := 1< fast.LastDelay() + u.tolerance { 82 | u.fastNode = fast 83 | } 84 | 85 | return u.fastNode, nil 86 | }) 87 | 88 | return elm.(C.Proxy) 89 | } 90 | 91 | func (u *URLTest) SupportUDP() bool { 92 | return u.fast().SupportUDP() 93 | } 94 | 95 | func (u *URLTest) MarshalJSON() ([]byte, error) { 96 | var all []string 97 | for _, proxy := range u.proxies() { 98 | all = append(all, proxy.Name()) 99 | } 100 | return json.Marshal(map[string]interface{}{ 101 | "type": u.Type().String(), 102 | "now": u.Now(), 103 | "all": all, 104 | }) 105 | } 106 | 107 | func parseURLTestOption(config map[string]interface{}) []urlTestOption { 108 | opts := []urlTestOption{} 109 | 110 | // tolerance 111 | if elm, ok := config["tolerance"]; ok { 112 | if tolerance, ok := elm.(int); ok { 113 | opts = append(opts, urlTestWithTolerance(uint16(tolerance))) 114 | } 115 | } 116 | 117 | return opts 118 | } 119 | 120 | func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { 121 | urlTest := &URLTest{ 122 | Base: outbound.NewBase(name, "", C.URLTest, false), 123 | single: singledo.NewSingle(defaultGetProxiesDuration), 124 | fastSingle: singledo.NewSingle(time.Second * 10), 125 | providers: providers, 126 | } 127 | 128 | for _, option := range options { 129 | option(urlTest) 130 | } 131 | 132 | return urlTest 133 | } 134 | -------------------------------------------------------------------------------- /hub/route/configs.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | "path/filepath" 6 | 7 | "github.com/Dreamacro/clash/config" 8 | "github.com/Dreamacro/clash/hub/executor" 9 | "github.com/Dreamacro/clash/log" 10 | P "github.com/Dreamacro/clash/proxy" 11 | "github.com/Dreamacro/clash/tunnel" 12 | 13 | "github.com/go-chi/chi" 14 | "github.com/go-chi/render" 15 | ) 16 | 17 | func configRouter() http.Handler { 18 | r := chi.NewRouter() 19 | r.Get("/", getConfigs) 20 | r.Put("/", updateConfigs) 21 | r.Patch("/", patchConfigs) 22 | return r 23 | } 24 | 25 | type configSchema struct { 26 | Port *int `json:"port"` 27 | SocksPort *int `json:"socks-port"` 28 | RedirPort *int `json:"redir-port"` 29 | MixedPort *int `json:"mixed-port"` 30 | AllowLan *bool `json:"allow-lan"` 31 | BindAddress *string `json:"bind-address"` 32 | Mode *tunnel.TunnelMode `json:"mode"` 33 | LogLevel *log.LogLevel `json:"log-level"` 34 | } 35 | 36 | func getConfigs(w http.ResponseWriter, r *http.Request) { 37 | general := executor.GetGeneral() 38 | render.JSON(w, r, general) 39 | } 40 | 41 | func pointerOrDefault(p *int, def int) int { 42 | if p != nil { 43 | return *p 44 | } 45 | 46 | return def 47 | } 48 | 49 | func patchConfigs(w http.ResponseWriter, r *http.Request) { 50 | general := &configSchema{} 51 | if err := render.DecodeJSON(r.Body, general); err != nil { 52 | render.Status(r, http.StatusBadRequest) 53 | render.JSON(w, r, ErrBadRequest) 54 | return 55 | } 56 | 57 | if general.AllowLan != nil { 58 | P.SetAllowLan(*general.AllowLan) 59 | } 60 | 61 | if general.BindAddress != nil { 62 | P.SetBindAddress(*general.BindAddress) 63 | } 64 | 65 | ports := P.GetPorts() 66 | P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) 67 | P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) 68 | P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) 69 | P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) 70 | 71 | if general.Mode != nil { 72 | tunnel.SetMode(*general.Mode) 73 | } 74 | 75 | if general.LogLevel != nil { 76 | log.SetLevel(*general.LogLevel) 77 | } 78 | 79 | render.NoContent(w, r) 80 | } 81 | 82 | type updateConfigRequest struct { 83 | Path string `json:"path"` 84 | Payload string `json:"payload"` 85 | } 86 | 87 | func updateConfigs(w http.ResponseWriter, r *http.Request) { 88 | req := updateConfigRequest{} 89 | if err := render.DecodeJSON(r.Body, &req); err != nil { 90 | render.Status(r, http.StatusBadRequest) 91 | render.JSON(w, r, ErrBadRequest) 92 | return 93 | } 94 | 95 | force := r.URL.Query().Get("force") == "true" 96 | var cfg *config.Config 97 | var err error 98 | 99 | if req.Payload != "" { 100 | cfg, err = executor.ParseWithBytes([]byte(req.Payload)) 101 | if err != nil { 102 | render.Status(r, http.StatusBadRequest) 103 | render.JSON(w, r, newError(err.Error())) 104 | return 105 | } 106 | } else { 107 | if !filepath.IsAbs(req.Path) { 108 | render.Status(r, http.StatusBadRequest) 109 | render.JSON(w, r, newError("path is not a absoluted path")) 110 | return 111 | } 112 | 113 | cfg, err = executor.ParseWithPath(req.Path) 114 | if err != nil { 115 | render.Status(r, http.StatusBadRequest) 116 | render.JSON(w, r, newError(err.Error())) 117 | return 118 | } 119 | } 120 | 121 | executor.ApplyConfig(cfg, force) 122 | render.NoContent(w, r) 123 | } 124 | -------------------------------------------------------------------------------- /adapters/outbound/http.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | "strconv" 15 | 16 | "github.com/Dreamacro/clash/component/dialer" 17 | C "github.com/Dreamacro/clash/constant" 18 | ) 19 | 20 | type Http struct { 21 | *Base 22 | user string 23 | pass string 24 | tlsConfig *tls.Config 25 | } 26 | 27 | type HttpOption struct { 28 | Name string `proxy:"name"` 29 | Server string `proxy:"server"` 30 | Port int `proxy:"port"` 31 | UserName string `proxy:"username,omitempty"` 32 | Password string `proxy:"password,omitempty"` 33 | TLS bool `proxy:"tls,omitempty"` 34 | SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 35 | } 36 | 37 | func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 38 | if h.tlsConfig != nil { 39 | cc := tls.Client(c, h.tlsConfig) 40 | err := cc.Handshake() 41 | c = cc 42 | if err != nil { 43 | return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 44 | } 45 | } 46 | 47 | if err := h.shakeHand(metadata, c); err != nil { 48 | return nil, err 49 | } 50 | return c, nil 51 | } 52 | 53 | func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 54 | c, err := dialer.DialContext(ctx, "tcp", h.addr) 55 | if err != nil { 56 | return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 57 | } 58 | tcpKeepAlive(c) 59 | 60 | c, err = h.StreamConn(c, metadata) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return NewConn(c, h), nil 66 | } 67 | 68 | func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 69 | addr := metadata.RemoteAddress() 70 | req := &http.Request{ 71 | Method: http.MethodConnect, 72 | URL: &url.URL{ 73 | Host: addr, 74 | }, 75 | Host: addr, 76 | Header: http.Header{ 77 | "Proxy-Connection": []string{"Keep-Alive"}, 78 | }, 79 | } 80 | 81 | if h.user != "" && h.pass != "" { 82 | auth := h.user + ":" + h.pass 83 | req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 84 | } 85 | 86 | if err := req.Write(rw); err != nil { 87 | return err 88 | } 89 | 90 | resp, err := http.ReadResponse(bufio.NewReader(rw), req) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | if resp.StatusCode == http.StatusOK { 96 | return nil 97 | } 98 | 99 | if resp.StatusCode == http.StatusProxyAuthRequired { 100 | return errors.New("HTTP need auth") 101 | } 102 | 103 | if resp.StatusCode == http.StatusMethodNotAllowed { 104 | return errors.New("CONNECT method not allowed by proxy") 105 | } 106 | 107 | if resp.StatusCode >= http.StatusInternalServerError { 108 | return errors.New(resp.Status) 109 | } 110 | 111 | return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 112 | } 113 | 114 | func NewHttp(option HttpOption) *Http { 115 | var tlsConfig *tls.Config 116 | if option.TLS { 117 | tlsConfig = &tls.Config{ 118 | InsecureSkipVerify: option.SkipCertVerify, 119 | ClientSessionCache: getClientSessionCache(), 120 | ServerName: option.Server, 121 | } 122 | } 123 | 124 | return &Http{ 125 | Base: &Base{ 126 | name: option.Name, 127 | addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 128 | tp: C.Http, 129 | }, 130 | user: option.UserName, 131 | pass: option.Password, 132 | tlsConfig: tlsConfig, 133 | } 134 | } 135 | --------------------------------------------------------------------------------