Compare commits

...

35 Commits

Author SHA1 Message Date
dff1e8f1ce Chore: update dependencies 2021-07-03 21:01:41 +08:00
995aa7a8fc Fix: remove ClientSessionCache and add NextProtos for vmess to fix #1468 2021-07-03 20:34:44 +08:00
3ca5d17c40 Fix: enable DNS server message compression (#1451) 2021-06-24 13:38:44 +08:00
244cb370a4 Change: config reload API use default path when both path and payload don't exist (#1447) 2021-06-21 17:33:34 +08:00
c35cb24bda Chore: use unix.ByteSliceToString transform cstring 2021-06-15 21:03:47 +08:00
b6ff08074c Refactor: plain http proxy (#1443) 2021-06-15 17:13:40 +08:00
70d53fd45a Chore: update development wiki to README.md 2021-06-13 23:11:49 +08:00
f231a63e93 Chore: Listener should not expose original net.Listener 2021-06-13 23:05:22 +08:00
6091fcdfec Style: code style 2021-06-13 17:23:10 +08:00
bcfc15e398 chore: expose udp field to proxies API (#1441) 2021-06-10 15:08:33 +08:00
045edc188c Style: code style 2021-06-10 14:05:56 +08:00
0778591524 Feature: dns resolve domain through nameserver-policy (#1406) 2021-05-19 11:17:35 +08:00
d5e52bed43 Feature: add protocol test 2021-05-17 20:33:00 +08:00
06fdd3abe0 Fix: vmess http should use Host header on request 2021-05-16 20:05:41 +08:00
4e5898197a Fix: build broken 2021-05-13 22:39:33 +08:00
f96ebab99f Chore: split component to transport 2021-05-13 22:19:34 +08:00
3c54f99fea Chore: update dependencies 2021-05-08 19:29:12 +08:00
824f5bd731 Fix: reuse http connection broken on previous commit 2021-05-07 11:08:46 +08:00
3f3db8476e Fix: HTTP inbound leak 2021-05-06 22:34:37 +08:00
f375f080da Fix: skip deleted node from url-test group (#1378)
Co-authored-by: fish <fish@youme.im>
2021-05-01 17:21:09 +08:00
e19e9ef5a4 Style: code style 2021-04-29 11:23:14 +08:00
682e65cb54 Style: code style 2021-04-26 20:42:17 +08:00
16a6d409d9 Feature: add freebsd arm64 to Makefile (#1370) 2021-04-22 16:38:13 +08:00
4186bcf1b2 Fix: should write file if provider initialize from HTTP (#1365) 2021-04-19 17:40:38 +08:00
df5112175f Fix: io timeout when snell v2 reuse connection (#1362) 2021-04-19 14:36:06 +08:00
d9341a49ea Fix: trojan should safe close connection 2021-04-19 12:20:37 +08:00
4e9e4b6cde Fix: grpc transport concurrent write 2021-04-14 21:46:05 +08:00
936b7012ba Feature: PROCESS-NAME support freebsd 13, fix panic on unsupported platforms (#1351) 2021-04-14 17:57:17 +08:00
a9cbd9ec98 Fix: use bufio.Reader on grpc to avoid panic 2021-04-14 00:16:59 +08:00
c9943fb857 Fix: grpc implementation SetDeadline for udp issue 2021-04-13 23:34:33 +08:00
a40274e2a2 Fix: vmess aead writer concurrent write (#1350) 2021-04-13 23:32:53 +08:00
b59d45c660 Feature: add CodeQL security checks (#1349) 2021-04-13 21:25:55 +08:00
7b01e103c2 Chore: use correctly vmess http2 default host 2021-04-10 12:10:10 +08:00
93a8acecce Fix: vmess h2 use server as host if host option is empty 2021-04-09 18:15:46 +08:00
586bb91c0c Fix: grpc transport panic 2021-04-09 18:11:07 +08:00
163 changed files with 3456 additions and 1041 deletions

30
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: "CodeQL"
on:
push:
branches: [ master, dev ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -22,7 +22,8 @@ PLATFORM_LIST = \
linux-mips64 \ linux-mips64 \
linux-mips64le \ linux-mips64le \
freebsd-386 \ freebsd-386 \
freebsd-amd64 freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \ windows-386 \
@ -82,6 +83,9 @@ freebsd-386:
freebsd-amd64: freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386: windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe

View File

@ -40,6 +40,9 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
## Premium Release ## Premium Release
[Release](https://github.com/Dreamacro/clash/releases/tag/premium) [Release](https://github.com/Dreamacro/clash/releases/tag/premium)
## Development
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Credits ## Credits
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)

View File

@ -1,11 +1,12 @@
package outbound package adapter
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "fmt"
"net" "net"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/Dreamacro/clash/common/queue" "github.com/Dreamacro/clash/common/queue"
@ -14,101 +15,25 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
) )
type Base struct {
name string
addr string
tp C.AdapterType
udp bool
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
func (b *Base) Addr() string {
return b.addr
}
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
return nil
}
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
return &Base{name, addr, tp, udp}
}
type conn struct {
net.Conn
chain C.Chain
}
func (c *conn) Chains() C.Chain {
return c.chain
}
func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type packetConn struct {
net.PacketConn
chain C.Chain
}
func (c *packetConn) Chains() C.Chain {
return c.chain
}
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue history *queue.Queue
alive *atomic.Bool alive *atomic.Bool
} }
// Alive implements C.Proxy
func (p *Proxy) Alive() bool { func (p *Proxy) Alive() bool {
return p.alive.Load() return p.alive.Load()
} }
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel() defer cancel()
return p.DialContext(ctx, metadata) return p.DialContext(ctx, metadata)
} }
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata) conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil { if err != nil {
@ -117,6 +42,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return conn, err return conn, err
} }
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory { func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy() queue := p.history.Copy()
histories := []C.DelayHistory{} histories := []C.DelayHistory{}
@ -127,6 +53,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
} }
// LastDelay return last history record. if proxy is not alive, return the max value of uint16. // LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) { func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff var max uint16 = 0xffff
if !p.alive.Load() { if !p.alive.Load() {
@ -144,6 +71,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
return history.Delay return history.Delay
} }
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) { func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON() inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil { if err != nil {
@ -154,10 +82,12 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
json.Unmarshal(inner, &mapping) json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
return json.Marshal(mapping) return json.Marshal(mapping)
} }
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() { defer func() {
p.alive.Store(err == nil) p.alive.Store(err == nil)
@ -218,3 +148,31 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
func NewProxy(adapter C.ProxyAdapter) *Proxy { func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)} return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
} }
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
return
}

21
adapter/inbound/http.go Normal file
View File

@ -0,0 +1,21 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewHTTP receive normal http request and return HTTPContext
func NewHTTP(target string, source net.Addr, conn net.Conn) *context.ConnContext {
metadata := parseSocksAddr(socks5.ParseAddr(target))
metadata.NetWork = C.TCP
metadata.Type = C.HTTP
if ip, port, err := parseAddr(source.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return context.NewConnContext(conn, metadata)
}

View File

@ -8,7 +8,7 @@ import (
"github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/context"
) )
// NewHTTPS recieve CONNECT request and return ConnContext // NewHTTPS receive CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext { func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT metadata.Type = C.HTTPCONNECT

View File

@ -1,8 +1,8 @@
package inbound package inbound
import ( import (
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
// PacketAdapter is a UDP Packet adapter for socks/redir/tun // PacketAdapter is a UDP Packet adapter for socks/redir/tun

View File

@ -3,12 +3,12 @@ package inbound
import ( import (
"net" "net"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
) )
// NewSocket recieve TCP inbound and return ConnContext // NewSocket receive TCP inbound and return ConnContext
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext { func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP

View File

@ -6,8 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
func parseSocksAddr(target socks5.Addr) *C.Metadata { func parseSocksAddr(target socks5.Addr) *C.Metadata {

100
adapter/outbound/base.go Normal file
View File

@ -0,0 +1,100 @@
package outbound
import (
"encoding/json"
"errors"
"net"
C "github.com/Dreamacro/clash/constant"
)
type Base struct {
name string
addr string
tp C.AdapterType
udp bool
}
// Name implements C.ProxyAdapter
func (b *Base) Name() string {
return b.name
}
// Type implements C.ProxyAdapter
func (b *Base) Type() C.AdapterType {
return b.tp
}
// StreamConn implements C.ProxyAdapter
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
// DialUDP implements C.ProxyAdapter
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("no support")
}
// SupportUDP implements C.ProxyAdapter
func (b *Base) SupportUDP() bool {
return b.udp
}
// MarshalJSON implements C.ProxyAdapter
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
// Addr implements C.ProxyAdapter
func (b *Base) Addr() string {
return b.addr
}
// Unwrap implements C.ProxyAdapter
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
return nil
}
func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base {
return &Base{name, addr, tp, udp}
}
type conn struct {
net.Conn
chain C.Chain
}
// Chains implements C.Connection
func (c *conn) Chains() C.Chain {
return c.chain
}
// AppendToChains implements C.Connection
func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type packetConn struct {
net.PacketConn
chain C.Chain
}
// Chains implements C.Connection
func (c *packetConn) Chains() C.Chain {
return c.chain
}
// AppendToChains implements C.Connection
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}

View File

@ -12,6 +12,7 @@ type Direct struct {
*Base *Base
} }
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
address := net.JoinHostPort(metadata.String(), metadata.DstPort) address := net.JoinHostPort(metadata.String(), metadata.DstPort)
@ -23,6 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return NewConn(c, d), nil return NewConn(c, d), nil
} }
// DialUDP implements C.ProxyAdapter
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket("udp", "")
if err != nil { if err != nil {

View File

@ -35,6 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if h.tlsConfig != nil { if h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig) cc := tls.Client(c, h.tlsConfig)
@ -51,6 +52,7 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, nil return c, nil
} }
// DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr) c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil { if err != nil {
@ -123,7 +125,6 @@ func NewHttp(option HttpOption) *Http {
} }
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: sni, ServerName: sni,
} }
} }

View File

@ -14,10 +14,12 @@ type Reject struct {
*Base *Base
} }
// DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return NewConn(&NopConn{}, r), nil return NewConn(&NopConn{}, r), nil
} }
// DialUDP implements C.ProxyAdapter
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("match reject rule") return nil, errors.New("match reject rule")
} }

View File

@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -10,10 +9,10 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/socks5"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
) )
@ -54,6 +53,7 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
@ -73,6 +73,7 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
return c, err return c, err
} }
// DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
@ -86,6 +87,7 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_
return NewConn(c, ss), err return NewConn(c, ss), err
} }
// DialUDP implements C.ProxyAdapter
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket("udp", "")
if err != nil { if err != nil {
@ -102,12 +104,6 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
} }
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
@ -153,7 +149,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if opts.TLS { if opts.TLS {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.SessionCache = getClientSessionCache()
} }
} }

View File

@ -2,15 +2,14 @@ package outbound
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/ssr/obfs"
"github.com/Dreamacro/clash/component/ssr/protocol"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowaead"
@ -37,6 +36,7 @@ type ShadowSocksROption struct {
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = ssr.obfs.StreamConn(c) c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c) c = ssr.cipher.StreamConn(c)
@ -58,6 +58,7 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
return c, err return c, err
} }
// DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ssr.addr) c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil { if err != nil {
@ -71,6 +72,7 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata)
return NewConn(c, ssr), err return NewConn(c, ssr), err
} }
// DialUDP implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "") pc, err := dialer.ListenPacket("udp", "")
if err != nil { if err != nil {
@ -88,12 +90,6 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
} }
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ssr.Type().String(),
})
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher

View File

@ -8,9 +8,9 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell"
) )
type Snell struct { type Snell struct {
@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
return snell.StreamConn(c, option.psk, option.version) return snell.StreamConn(c, option.psk, option.version)
} }
// StreamConn implements C.ProxyAdapter
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.Atoi(metadata.DstPort)
@ -55,6 +56,7 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, err return c, err
} }
// DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
if s.version == snell.Version2 { if s.version == snell.Version2 {
c, err := s.pool.Get() c, err := s.pool.Get()

View File

@ -11,8 +11,8 @@ import (
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
type Socks5 struct { type Socks5 struct {
@ -35,6 +35,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if ss.tls { if ss.tls {
cc := tls.Client(c, ss.tlsConfig) cc := tls.Client(c, ss.tlsConfig)
@ -58,6 +59,7 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, nil return c, nil
} }
// DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
@ -75,8 +77,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Co
return NewConn(c, ss), nil return NewConn(c, ss), nil
} }
// DialUDP implements C.ProxyAdapter
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel() defer cancel()
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
@ -142,7 +145,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
if option.TLS { if option.TLS {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server, ServerName: option.Server,
} }
} }

View File

@ -3,15 +3,14 @@ package outbound
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/gun"
"github.com/Dreamacro/clash/component/trojan"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@ -39,6 +38,7 @@ type TrojanOption struct {
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
if t.transport != nil { if t.transport != nil {
@ -55,6 +55,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, err return c, err
} }
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
// gun transport // gun transport
if t.transport != nil { if t.transport != nil {
@ -87,6 +88,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
return NewConn(c, t), err return NewConn(c, t), err
} }
// DialUDP implements C.ProxyAdapter
func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
var c net.Conn var c net.Conn
@ -96,23 +98,22 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
defer safeConnClose(c, err)
} else { } else {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel() defer cancel()
c, err = dialer.DialContext(ctx, "tcp", t.addr) c, err = dialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
defer safeConnClose(c, err)
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = t.instance.StreamConn(c) c, err = t.instance.StreamConn(c)
if err != nil { if err != nil {
c.Close()
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
} }
defer safeConnClose(c, err)
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil { if err != nil {
return nil, err return nil, err
@ -122,21 +123,14 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
func (t *Trojan) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": t.Type().String(),
})
}
func NewTrojan(option TrojanOption) (*Trojan, error) { func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{ tOption := &trojan.Option{
Password: option.Password, Password: option.Password,
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
} }
if option.SNI != "" { if option.SNI != "" {
@ -168,7 +162,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
InsecureSkipVerify: tOption.SkipCertVerify, InsecureSkipVerify: tOption.SkipCertVerify,
ServerName: tOption.ServerName, ServerName: tOption.ServerName,
ClientSessionCache: getClientSessionCache(),
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)

View File

@ -2,56 +2,15 @@ package outbound
import ( import (
"bytes" "bytes"
"crypto/tls"
"fmt"
"net" "net"
"net/url"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
) )
const (
tcpTimeout = 5 * time.Second
)
var (
globalClientSessionCache tls.ClientSessionCache
once sync.Once
)
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
return
}
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) tcp.SetKeepAlive(true)
@ -59,13 +18,6 @@ func tcpKeepAlive(c net.Conn) {
} }
} }
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte { func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte var buf [][]byte
aType := uint8(metadata.AddrType) aType := uint8(metadata.AddrType)

View File

@ -11,10 +11,10 @@ import (
"strings" "strings"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/gun"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@ -64,6 +64,7 @@ type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"` GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
} }
// StreamConn implements C.ProxyAdapter
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
switch v.option.Network { switch v.option.Network {
@ -85,7 +86,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if v.option.TLS { if v.option.TLS {
wsOpts.TLS = true wsOpts.TLS = true
wsOpts.SessionCache = getClientSessionCache()
wsOpts.SkipCertVerify = v.option.SkipCertVerify wsOpts.SkipCertVerify = v.option.SkipCertVerify
wsOpts.ServerName = v.option.ServerName wsOpts.ServerName = v.option.ServerName
} }
@ -97,7 +97,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := &vmess.TLSConfig{ tlsOpts := &vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -124,7 +123,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := vmess.TLSConfig{ tlsOpts := vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
} }
@ -152,7 +150,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := &vmess.TLSConfig{ tlsOpts := &vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -170,6 +167,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return v.client.StreamConn(c, parseVmessAddr(metadata)) return v.client.StreamConn(c, parseVmessAddr(metadata))
} }
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
// gun transport // gun transport
if v.transport != nil { if v.transport != nil {
@ -198,6 +196,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
return NewConn(c, v), err return NewConn(c, v), err
} }
// DialUDP implements C.ProxyAdapter
func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
@ -219,7 +218,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
c, err = v.client.StreamConn(c, parseVmessAddr(metadata)) c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
} else { } else {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel() defer cancel()
c, err = dialer.DialContext(ctx, "tcp", v.addr) c, err = dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
@ -270,7 +269,12 @@ func NewVmess(option VmessOption) (*Vmess, error) {
option: &option, option: &option,
} }
if option.Network == "grpc" { switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(network, addr string) (net.Conn, error) {
c, err := dialer.DialContext(context.Background(), "tcp", v.addr) c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil { if err != nil {

View File

@ -3,7 +3,7 @@ package outboundgroup
import ( import (
"time" "time"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )

View File

@ -4,8 +4,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
return proxy.Name() return proxy.Name()
} }
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy(true) proxy := f.findAliveProxy(true)
c, err := proxy.DialContext(ctx, metadata) c, err := proxy.DialContext(ctx, metadata)
@ -31,6 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err return c, err
} }
// DialUDP implements C.ProxyAdapter
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy(true) proxy := f.findAliveProxy(true)
pc, err := proxy.DialUDP(metadata) pc, err := proxy.DialUDP(metadata)
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
// SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool { func (f *Fallback) SupportUDP() bool {
if f.disableUDP { if f.disableUDP {
return false return false
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
return proxy.SupportUDP() return proxy.SupportUDP()
} }
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range f.proxies(false) { for _, proxy := range f.proxies(false) {
@ -61,6 +65,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
}) })
} }
// Unwrap implements C.ProxyAdapter
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
proxy := f.findAliveProxy(true) proxy := f.findAliveProxy(true)
return proxy return proxy

View File

@ -7,8 +7,8 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -68,6 +68,7 @@ func jumpHash(key uint64, buckets int32) int32 {
return int32(b) return int32(b)
} }
// DialContext implements C.ProxyAdapter
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
@ -81,6 +82,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
return return
} }
// DialUDP implements C.ProxyAdapter
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
@ -93,6 +95,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error
return proxy.DialUDP(metadata) return proxy.DialUDP(metadata)
} }
// SupportUDP implements C.ProxyAdapter
func (lb *LoadBalance) SupportUDP() bool { func (lb *LoadBalance) SupportUDP() bool {
return !lb.disableUDP return !lb.disableUDP
} }
@ -130,6 +133,7 @@ func strategyConsistentHashing() strategyFn {
} }
} }
// Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
proxies := lb.proxies(true) proxies := lb.proxies(true)
return lb.strategyFn(proxies, metadata) return lb.strategyFn(proxies, metadata)
@ -143,6 +147,7 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
return elm.([]C.Proxy) return elm.([]C.Proxy)
} }
// MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range lb.proxies(false) { for _, proxy := range lb.proxies(false) {

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )

View File

@ -6,8 +6,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -19,6 +19,7 @@ type Relay struct {
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies := r.proxies(metadata, true) proxies := r.proxies(metadata, true)
if len(proxies) == 0 { if len(proxies) == 0 {
@ -56,6 +57,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return outbound.NewConn(c, r), nil return outbound.NewConn(c, r), nil
} }
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range r.rawProxies(false) { for _, proxy := range r.rawProxies(false) {

View File

@ -5,8 +5,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -19,6 +19,7 @@ type Selector struct {
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
// DialContext implements C.ProxyAdapter
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selectedProxy(true).DialContext(ctx, metadata) c, err := s.selectedProxy(true).DialContext(ctx, metadata)
if err == nil { if err == nil {
@ -27,6 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err return c, err
} }
// DialUDP implements C.ProxyAdapter
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := s.selectedProxy(true).DialUDP(metadata) pc, err := s.selectedProxy(true).DialUDP(metadata)
if err == nil { if err == nil {
@ -35,6 +37,7 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
// SupportUDP implements C.ProxyAdapter
func (s *Selector) SupportUDP() bool { func (s *Selector) SupportUDP() bool {
if s.disableUDP { if s.disableUDP {
return false return false
@ -43,6 +46,7 @@ func (s *Selector) SupportUDP() bool {
return s.selectedProxy(false).SupportUDP() return s.selectedProxy(false).SupportUDP()
} }
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range getProvidersProxies(s.providers, false) { for _, proxy := range getProvidersProxies(s.providers, false) {
@ -72,6 +76,7 @@ func (s *Selector) Set(name string) error {
return errors.New("proxy not exist") return errors.New("proxy not exist")
} }
// Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
return s.selectedProxy(true) return s.selectedProxy(true)
} }

View File

@ -5,8 +5,8 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -33,6 +33,7 @@ func (u *URLTest) Now() string {
return u.fast(false).Name() return u.fast(false).Name()
} }
// DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
c, err = u.fast(true).DialContext(ctx, metadata) c, err = u.fast(true).DialContext(ctx, metadata)
if err == nil { if err == nil {
@ -41,6 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
return c, err return c, err
} }
// DialUDP implements C.ProxyAdapter
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := u.fast(true).DialUDP(metadata) pc, err := u.fast(true).DialUDP(metadata)
if err == nil { if err == nil {
@ -49,6 +51,7 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err return pc, err
} }
// Unwrap implements C.ProxyAdapter
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
return u.fast(true) return u.fast(true)
} }
@ -66,7 +69,13 @@ func (u *URLTest) fast(touch bool) C.Proxy {
proxies := u.proxies(touch) proxies := u.proxies(touch)
fast := proxies[0] fast := proxies[0]
min := fast.LastDelay() min := fast.LastDelay()
fastNotExist := true
for _, proxy := range proxies[1:] { for _, proxy := range proxies[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
if !proxy.Alive() { if !proxy.Alive() {
continue continue
} }
@ -79,7 +88,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
} }
// tolerance // tolerance
if u.fastNode == nil || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast u.fastNode = fast
} }
@ -89,6 +98,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
return elm.(C.Proxy) return elm.(C.Proxy)
} }
// SupportUDP implements C.ProxyAdapter
func (u *URLTest) SupportUDP() bool { func (u *URLTest) SupportUDP() bool {
if u.disableUDP { if u.disableUDP {
return false return false
@ -97,6 +107,7 @@ func (u *URLTest) SupportUDP() bool {
return u.fast(false).SupportUDP() return u.fast(false).SupportUDP()
} }
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range u.proxies(false) { for _, proxy := range u.proxies(false) {

View File

@ -1,8 +1,9 @@
package outbound package adapter
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -20,36 +21,36 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
) )
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &ShadowSocksOption{} ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewShadowSocks(*ssOption) proxy, err = outbound.NewShadowSocks(*ssOption)
case "ssr": case "ssr":
ssrOption := &ShadowSocksROption{} ssrOption := &outbound.ShadowSocksROption{}
err = decoder.Decode(mapping, ssrOption) err = decoder.Decode(mapping, ssrOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewShadowSocksR(*ssrOption) proxy, err = outbound.NewShadowSocksR(*ssrOption)
case "socks5": case "socks5":
socksOption := &Socks5Option{} socksOption := &outbound.Socks5Option{}
err = decoder.Decode(mapping, socksOption) err = decoder.Decode(mapping, socksOption)
if err != nil { if err != nil {
break break
} }
proxy = NewSocks5(*socksOption) proxy = outbound.NewSocks5(*socksOption)
case "http": case "http":
httpOption := &HttpOption{} httpOption := &outbound.HttpOption{}
err = decoder.Decode(mapping, httpOption) err = decoder.Decode(mapping, httpOption)
if err != nil { if err != nil {
break break
} }
proxy = NewHttp(*httpOption) proxy = outbound.NewHttp(*httpOption)
case "vmess": case "vmess":
vmessOption := &VmessOption{ vmessOption := &outbound.VmessOption{
HTTPOpts: HTTPOptions{ HTTPOpts: outbound.HTTPOptions{
Method: "GET", Method: "GET",
Path: []string{"/"}, Path: []string{"/"},
}, },
@ -58,21 +59,21 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
if err != nil { if err != nil {
break break
} }
proxy, err = NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "snell": case "snell":
snellOption := &SnellOption{} snellOption := &outbound.SnellOption{}
err = decoder.Decode(mapping, snellOption) err = decoder.Decode(mapping, snellOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &TrojanOption{} trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break
} }
proxy, err = NewTrojan(*trojanOption) proxy, err = outbound.NewTrojan(*trojanOption)
default: default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
} }

View File

@ -72,6 +72,8 @@ func (f *fetcher) Initial() (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
isLocal = false
} }
if f.vehicle.Type() != File && !isLocal { if f.vehicle.Type() != File && !isLocal {

View File

@ -7,7 +7,7 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -133,7 +133,7 @@ func proxiesParse(buf []byte) (interface{}, error) {
proxies := []C.Proxy{} proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
proxy, err := outbound.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err) return nil, fmt.Errorf("proxy %d error: %w", idx, err)
} }

View File

@ -1,4 +1,4 @@
package mixed package net
import ( import (
"bufio" "bufio"

View File

@ -1,38 +0,0 @@
// Copy from: https://github.com/Equim-chan/leb128
// License: BSD-3-Clause
package gun
var sevenbits = [...]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
}
func appendUleb128(b []byte, v uint64) []byte {
// If it's less than or equal to 7-bit
if v < 0x80 {
return append(b, sevenbits[v])
}
for {
c := uint8(v & 0x7f)
v >>= 7
if v != 0 {
c |= 0x80
}
b = append(b, c)
if c&0x80 == 0 {
break
}
}
return b
}

View File

@ -1,12 +1,13 @@
package process package process
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"net" "net"
"path/filepath" "path/filepath"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/unix"
) )
const ( const (
@ -94,12 +95,8 @@ func getExecPathFromPID(pid uint32) (string, error) {
if errno != 0 { if errno != 0 {
return "", errno return "", errno
} }
firstZero := bytes.IndexByte(buf, 0)
if firstZero <= 0 {
return "", nil
}
return filepath.Base(string(buf[:firstZero])), nil return filepath.Base(unix.ByteSliceToString(buf)), nil
} }
func readNativeUint32(b []byte) uint32 { func readNativeUint32(b []byte) uint32 {

View File

@ -30,6 +30,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
} }
}) })
if defaultSearcher == nil {
return "", ErrPlatformNotSupport
}
var spath string var spath string
isTCP := network == TCP isTCP := network == TCP
switch network { switch network {
@ -190,6 +194,8 @@ func newSearcher(major int) *searcher {
udpInpOffset: 8, udpInpOffset: 8,
} }
case 12: case 12:
fallthrough
case 13:
s = &searcher{ s = &searcher{
headSize: 64, headSize: 64,
tcpItemSize: 744, tcpItemSize: 744,

View File

@ -23,7 +23,7 @@ type DomainTrie struct {
root *Node root *Node
} }
func validAndSplitDomain(domain string) ([]string, bool) { func ValidAndSplitDomain(domain string) ([]string, bool) {
if domain != "" && domain[len(domain)-1] == '.' { if domain != "" && domain[len(domain)-1] == '.' {
return nil, false return nil, false
} }
@ -54,7 +54,7 @@ func validAndSplitDomain(domain string) ([]string, bool) {
// 4. .example.com // 4. .example.com
// 5. +.example.com // 5. +.example.com
func (t *DomainTrie) Insert(domain string, data interface{}) error { func (t *DomainTrie) Insert(domain string, data interface{}) error {
parts, valid := validAndSplitDomain(domain) parts, valid := ValidAndSplitDomain(domain)
if !valid { if !valid {
return ErrInvalidDomain return ErrInvalidDomain
} }
@ -91,7 +91,7 @@ func (t *DomainTrie) insert(parts []string, data interface{}) {
// 2. wildcard domain // 2. wildcard domain
// 2. dot wildcard domain // 2. dot wildcard domain
func (t *DomainTrie) Search(domain string) *Node { func (t *DomainTrie) Search(domain string) *Node {
parts, valid := validAndSplitDomain(domain) parts, valid := ValidAndSplitDomain(domain)
if !valid || parts[0] == "" { if !valid || parts[0] == "" {
return nil return nil
} }

View File

@ -8,16 +8,17 @@ import (
"os" "os"
"strings" "strings"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
R "github.com/Dreamacro/clash/rules" R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@ -64,6 +65,7 @@ type DNS struct {
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
NameServerPolicy map[string]dns.NameServer
} }
// FallbackFilter config // FallbackFilter config
@ -106,6 +108,7 @@ type RawDNS struct {
FakeIPRange string `yaml:"fake-ip-range"` FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
} }
type RawFallbackFilter struct { type RawFallbackFilter struct {
@ -272,13 +275,13 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
proxyList = append(proxyList, "DIRECT", "REJECT") proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
proxy, err := outbound.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
} }
@ -337,7 +340,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName) return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
} }
proxies[groupName] = outbound.NewProxy(group) proxies[groupName] = adapter.NewProxy(group)
} }
// initial compatible provider // initial compatible provider
@ -366,7 +369,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
}, },
[]provider.ProxyProvider{pd}, []provider.ProxyProvider{pd},
) )
proxies["GLOBAL"] = outbound.NewProxy(global) proxies["GLOBAL"] = adapter.NewProxy(global)
return proxies, providersMap, nil return proxies, providersMap, nil
} }
@ -500,6 +503,23 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nameservers, nil return nameservers, nil
} }
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
policy := map[string]dns.NameServer{}
for domain, server := range nsPolicy {
nameservers, err := parseNameServer([]string{server})
if err != nil {
return nil, err
}
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
}
policy[domain] = nameservers[0]
}
return policy, nil
}
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
ipNets := []*net.IPNet{} ipNets := []*net.IPNet{}
@ -537,6 +557,10 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
return nil, err return nil, err
} }
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 { if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver") return nil, errors.New("default nameserver should have at least one nameserver")
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
) )

View File

@ -27,6 +27,10 @@ const (
LoadBalance LoadBalance
) )
const (
DefaultTCPTimeout = 5 * time.Second
)
type Connection interface { type Connection interface {
Chains() Chain Chains() Chain
AppendToChains(adapter ProxyAdapter) AppendToChains(adapter ProxyAdapter)

View File

@ -1,47 +0,0 @@
package context
import (
"net"
"net/http"
C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
)
type HTTPContext struct {
id uuid.UUID
metadata *C.Metadata
conn net.Conn
req *http.Request
}
func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext {
id, _ := uuid.NewV4()
return &HTTPContext{
id: id,
metadata: metadata,
conn: conn,
req: req,
}
}
// ID implement C.ConnContext ID
func (hc *HTTPContext) ID() uuid.UUID {
return hc.id
}
// Metadata implement C.ConnContext Metadata
func (hc *HTTPContext) Metadata() *C.Metadata {
return hc.metadata
}
// Conn implement C.ConnContext Conn
func (hc *HTTPContext) Conn() net.Conn {
return hc.conn
}
// Request return the http request struct
func (hc *HTTPContext) Request() *http.Request {
return hc.req
}

View File

@ -3,7 +3,6 @@ package dns
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@ -76,7 +75,6 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return &dohClient{ return &dohClient{
url: url, url: url,
transport: &http.Transport{ transport: &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
ForceAttemptHTTP2: true, ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)

View File

@ -2,7 +2,6 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
@ -20,10 +19,6 @@ import (
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
var (
globalSessionCache = tls.NewLRUClientSessionCache(64)
)
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error) Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
@ -43,6 +38,7 @@ type Resolver struct {
fallbackIPFilters []fallbackIPFilter fallbackIPFilters []fallbackIPFilter
group singleflight.Group group singleflight.Group
lruCache *cache.LruCache lruCache *cache.LruCache
policy *trie.DomainTrie
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA // ResolveIP request with TypeA and TypeAAAA, priority return TypeA
@ -131,6 +127,9 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
return r.ipExchange(m) return r.ipExchange(m)
} }
if matched := r.matchPolicy(m); len(matched) != 0 {
return r.batchExchange(matched, m)
}
return r.batchExchange(r.main, m) return r.batchExchange(r.main, m)
}) })
@ -172,6 +171,24 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
return return
} }
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
if r.policy == nil {
return nil
}
domain := r.msgToDomain(m)
if domain == "" {
return nil
}
record := r.policy.Search(domain)
if record == nil {
return nil
}
return record.Data.([]dnsClient)
}
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 { if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
return false return false
@ -194,6 +211,11 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
if matched := r.matchPolicy(m); len(matched) != 0 {
res := <-r.asyncExchange(matched, m)
return res.Msg, res.Error
}
onlyFallback := r.shouldOnlyQueryFallback(m) onlyFallback := r.shouldOnlyQueryFallback(m)
if onlyFallback { if onlyFallback {
@ -293,6 +315,7 @@ type Config struct {
FallbackFilter FallbackFilter FallbackFilter FallbackFilter
Pool *fakeip.Pool Pool *fakeip.Pool
Hosts *trie.DomainTrie Hosts *trie.DomainTrie
Policy map[string]NameServer
} }
func NewResolver(config Config) *Resolver { func NewResolver(config Config) *Resolver {
@ -312,6 +335,13 @@ func NewResolver(config Config) *Resolver {
r.fallback = transform(config.Fallback, defaultResolver) r.fallback = transform(config.Fallback, defaultResolver)
} }
if len(config.Policy) != 0 {
r.policy = trie.New()
for domain, nameserver := range config.Policy {
r.policy.Insert(domain, transform([]NameServer{nameserver}, defaultResolver))
}
}
fallbackIPFilters := []fallbackIPFilter{} fallbackIPFilters := []fallbackIPFilter{}
if config.FallbackFilter.GeoIP { if config.FallbackFilter.GeoIP {
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{}) fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})

View File

@ -30,6 +30,7 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
D.HandleFailed(w, r) D.HandleFailed(w, r)
return return
} }
msg.Compress = true
w.WriteMsg(msg) w.WriteMsg(msg)
} }

View File

@ -127,7 +127,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
Client: &D.Client{ Client: &D.Client{
Net: s.Net, Net: s.Net,
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
ClientSessionCache: globalSessionCache,
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
NextProtos: []string{"dns"}, NextProtos: []string{"dns"},
ServerName: host, ServerName: host,

12
go.mod
View File

@ -4,19 +4,19 @@ go 1.16
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7 github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/go-chi/chi/v5 v5.0.2 github.com/go-chi/chi/v5 v5.0.3
github.com/go-chi/cors v1.2.0 github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.0.0+incompatible github.com/gofrs/uuid v4.0.0+incompatible
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.41 github.com/miekg/dns v1.1.43
github.com/oschwald/geoip2-golang v1.5.0 github.com/oschwald/geoip2-golang v1.5.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.7.0 go.uber.org/atomic v1.8.0
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )

30
go.sum
View File

@ -3,8 +3,8 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.2 h1:4xKeALZdMEsuI5s05PU2Bm89Uc5iM04qFubUCl5LfAQ= github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
github.com/go-chi/chi/v5 v5.0.2/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE= github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
@ -13,8 +13,8 @@ github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPh
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw= github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s= github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk= github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
@ -29,26 +29,28 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -6,9 +6,9 @@ import (
"os" "os"
"sync" "sync"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/profile" "github.com/Dreamacro/clash/component/profile"
@ -18,9 +18,9 @@ import (
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
P "github.com/Dreamacro/clash/listener"
authStore "github.com/Dreamacro/clash/listener/auth"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
authStore "github.com/Dreamacro/clash/proxy/auth"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
@ -128,6 +128,7 @@ func updateDNS(c *config.DNS) {
Domain: c.FallbackFilter.Domain, Domain: c.FallbackFilter.Domain,
}, },
Default: c.DefaultNameserver, Default: c.DefaultNameserver,
Policy: c.NameServerPolicy,
} }
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
@ -186,23 +187,26 @@ func updateGeneral(general *config.General, force bool) {
bindAddress := general.BindAddress bindAddress := general.BindAddress
P.SetBindAddress(bindAddress) P.SetBindAddress(bindAddress)
if err := P.ReCreateHTTP(general.Port); err != nil { tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn()
if err := P.ReCreateHTTP(general.Port, tcpIn); err != nil {
log.Errorln("Start HTTP server error: %s", err.Error()) log.Errorln("Start HTTP server error: %s", err.Error())
} }
if err := P.ReCreateSocks(general.SocksPort); err != nil { if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
log.Errorln("Start SOCKS5 server error: %s", err.Error()) log.Errorln("Start SOCKS5 server error: %s", err.Error())
} }
if err := P.ReCreateRedir(general.RedirPort); err != nil { if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
log.Errorln("Start Redir server error: %s", err.Error()) log.Errorln("Start Redir server error: %s", err.Error())
} }
if err := P.ReCreateTProxy(general.TProxyPort); err != nil { if err := P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn); err != nil {
log.Errorln("Start TProxy server error: %s", err.Error()) log.Errorln("Start TProxy server error: %s", err.Error())
} }
if err := P.ReCreateMixed(general.MixedPort); err != nil { if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
} }
} }
@ -231,7 +235,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
} }
for name, proxy := range proxies { for name, proxy := range proxies {
outbound, ok := proxy.(*outbound.Proxy) outbound, ok := proxy.(*adapter.Proxy)
if !ok { if !ok {
continue continue
} }

View File

@ -6,9 +6,10 @@ import (
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/executor"
P "github.com/Dreamacro/clash/listener"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -66,11 +67,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
} }
ports := P.GetPorts() ports := P.GetPorts()
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) tcpIn := tunnel.TCPIn()
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) udpIn := tunnel.UDPIn()
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn)
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
if general.Mode != nil { if general.Mode != nil {
tunnel.SetMode(*general.Mode) tunnel.SetMode(*general.Mode)
@ -112,6 +117,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
return return
} }
} else { } else {
if req.Path == "" {
req.Path = constant.Path.Config()
}
if !filepath.IsAbs(req.Path) { if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absolute path")) render.JSON(w, r, newError("path is not a absolute path"))

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"

View File

@ -7,8 +7,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/profile/cachefile"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
@ -78,7 +78,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return return
} }
proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy) proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector) selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok { if !ok {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)

39
listener/http/client.go Normal file
View File

@ -0,0 +1,39 @@
package http
import (
"context"
"errors"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
)
func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client {
return &http.Client{
Transport: &http.Transport{
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DialContext: func(context context.Context, network, address string) (net.Conn, error) {
if network != "tcp" && network != "tcp4" && network != "tcp6" {
return nil, errors.New("unsupported network " + network)
}
left, right := net.Pipe()
in <- inbound.NewHTTP(address, source, right)
return left, nil
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
}

10
listener/http/hack.go Normal file
View File

@ -0,0 +1,10 @@
package http
import (
"bufio"
"net/http"
_ "unsafe"
)
//go:linkname ReadRequest net/http.readRequest
func ReadRequest(b *bufio.Reader, deleteHostHeader bool) (req *http.Request, err error)

132
listener/http/proxy.go Normal file
View File

@ -0,0 +1,132 @@
package http
import (
"net"
"net/http"
"strings"
"time"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/cache"
N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
authStore "github.com/Dreamacro/clash/listener/auth"
"github.com/Dreamacro/clash/log"
)
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
client := newClient(c.RemoteAddr(), in)
defer client.CloseIdleConnections()
conn := N.NewBufferedConn(c)
keepAlive := true
trusted := cache == nil // disable authenticate if cache is nil
for keepAlive {
request, err := ReadRequest(conn.Reader(), false)
if err != nil {
break
}
request.RemoteAddr = conn.RemoteAddr().String()
keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
var resp *http.Response
if !trusted {
resp = authenticate(request, cache)
trusted = resp == nil
}
if trusted {
if request.Method == http.MethodConnect {
resp = responseWith(200)
resp.Status = "Connection established"
if resp.Write(conn) != nil {
break // close connection
}
in <- inbound.NewHTTPS(request, conn)
return // hijack connection
}
host := request.Header.Get("Host")
if host != "" {
request.Host = host
}
request.RequestURI = ""
RemoveHopByHopHeaders(request.Header)
RemoveExtraHTTPHostPort(request)
if request.URL.Scheme == "" || request.URL.Host == "" {
resp = responseWith(http.StatusBadRequest)
} else {
resp, err = client.Do(request)
if err != nil {
resp = responseWith(http.StatusBadGateway)
}
}
}
RemoveHopByHopHeaders(resp.Header)
if keepAlive {
resp.Header.Set("Proxy-Connection", "keep-alive")
resp.Header.Set("Connection", "keep-alive")
resp.Header.Set("Keep-Alive", "timeout=4")
}
resp.Close = !keepAlive
err = resp.Write(conn)
if err != nil {
break // close connection
}
}
conn.Close()
}
func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
authenticator := authStore.Authenticator()
if authenticator != nil {
credential := ParseBasicProxyAuthorization(request)
if credential == "" {
resp := responseWith(http.StatusProxyAuthRequired)
resp.Header.Set("Proxy-Authenticate", "Basic")
return resp
}
var authed interface{}
if authed = cache.Get(credential); authed == nil {
user, pass, err := DecodeBasicProxyAuthorization(credential)
authed = err == nil && authenticator.Verify(user, pass)
cache.Put(credential, authed, time.Minute)
}
if !authed.(bool) {
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(http.StatusForbidden)
}
}
return nil
}
func responseWith(statusCode int) *http.Response {
return &http.Response{
StatusCode: statusCode,
Status: http.StatusText(statusCode),
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{},
}
}

59
listener/http/server.go Normal file
View File

@ -0,0 +1,59 @@
package http
import (
"net"
"time"
"github.com/Dreamacro/clash/common/cache"
C "github.com/Dreamacro/clash/constant"
)
type Listener struct {
listener net.Listener
address string
closed bool
}
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
return NewWithAuthenticate(addr, in, true)
}
func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
var c *cache.Cache
if authenticate {
c = cache.New(time.Second * 30)
}
hl := &Listener{
listener: l,
address: addr,
}
go func() {
for {
conn, err := hl.listener.Accept()
if err != nil {
if hl.closed {
break
}
continue
}
go HandleConn(conn, in, c)
}
}()
return hl, nil
}
func (l *Listener) Close() {
l.closed = true
l.listener.Close()
}
func (l *Listener) Address() string {
return l.address
}

View File

@ -1,25 +1,13 @@
package inbound package http
import ( import (
"encoding/base64"
"errors"
"net" "net"
"net/http" "net/http"
"strings" "strings"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
) )
// NewHTTP recieve normal http request and return HTTPContext
func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTP
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return context.NewHTTPContext(conn, request, metadata)
}
// RemoveHopByHopHeaders remove hop-by-hop header // RemoveHopByHopHeaders remove hop-by-hop header
func RemoveHopByHopHeaders(header http.Header) { func RemoveHopByHopHeaders(header http.Header) {
// Strip hop-by-hop header based on RFC: // Strip hop-by-hop header based on RFC:
@ -59,3 +47,28 @@ func RemoveExtraHTTPHostPort(req *http.Request) {
req.Host = host req.Host = host
req.URL.Host = host req.URL.Host = host
} }
// ParseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential
func ParseBasicProxyAuthorization(request *http.Request) string {
value := request.Header.Get("Proxy-Authorization")
if !strings.HasPrefix(value, "Basic ") {
return ""
}
return value[6:] // value[len("Basic "):]
}
// DecodeBasicProxyAuthorization decode base64-encoded credential
func DecodeBasicProxyAuthorization(credential string) (string, string, error) {
plain, err := base64.StdEncoding.DecodeString(credential)
if err != nil {
return "", "", err
}
login := strings.Split(string(plain), ":")
if len(login) != 2 {
return "", "", errors.New("invalid login")
}
return login[0], login[1], nil
}

View File

@ -6,26 +6,29 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/http"
"github.com/Dreamacro/clash/listener/mixed"
"github.com/Dreamacro/clash/listener/redir"
"github.com/Dreamacro/clash/listener/socks"
"github.com/Dreamacro/clash/listener/tproxy"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/mixed"
"github.com/Dreamacro/clash/proxy/redir"
"github.com/Dreamacro/clash/proxy/socks"
) )
var ( var (
allowLan = false allowLan = false
bindAddress = "*" bindAddress = "*"
socksListener *socks.SockListener socksListener *socks.Listener
socksUDPListener *socks.SockUDPListener socksUDPListener *socks.UDPListener
httpListener *http.HTTPListener httpListener *http.Listener
redirListener *redir.RedirListener redirListener *redir.Listener
redirUDPListener *redir.RedirUDPListener redirUDPListener *tproxy.UDPListener
tproxyListener *redir.TProxyListener tproxyListener *tproxy.Listener
tproxyUDPListener *redir.RedirUDPListener tproxyUDPListener *tproxy.UDPListener
mixedListener *mixed.MixedListener mixedListener *mixed.Listener
mixedUDPLister *socks.SockUDPListener mixedUDPLister *socks.UDPListener
// lock for recreate function // lock for recreate function
socksMux sync.Mutex socksMux sync.Mutex
@ -59,7 +62,7 @@ func SetBindAddress(host string) {
bindAddress = host bindAddress = host
} }
func ReCreateHTTP(port int) error { func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) error {
httpMux.Lock() httpMux.Lock()
defer httpMux.Unlock() defer httpMux.Unlock()
@ -78,15 +81,16 @@ func ReCreateHTTP(port int) error {
} }
var err error var err error
httpListener, err = http.NewHTTPProxy(addr) httpListener, err = http.New(addr, tcpIn)
if err != nil { if err != nil {
return err return err
} }
log.Infoln("HTTP proxy listening at: %s", httpListener.Address())
return nil return nil
} }
func ReCreateSocks(port int) error { func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
socksMux.Lock() socksMux.Lock()
defer socksMux.Unlock() defer socksMux.Unlock()
@ -121,12 +125,12 @@ func ReCreateSocks(port int) error {
return nil return nil
} }
tcpListener, err := socks.NewSocksProxy(addr) tcpListener, err := socks.New(addr, tcpIn)
if err != nil { if err != nil {
return err return err
} }
udpListener, err := socks.NewSocksUDPProxy(addr) udpListener, err := socks.NewUDP(addr, udpIn)
if err != nil { if err != nil {
tcpListener.Close() tcpListener.Close()
return err return err
@ -135,10 +139,11 @@ func ReCreateSocks(port int) error {
socksListener = tcpListener socksListener = tcpListener
socksUDPListener = udpListener socksUDPListener = udpListener
log.Infoln("SOCKS5 proxy listening at: %s", socksListener.Address())
return nil return nil
} }
func ReCreateRedir(port int) error { func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
redirMux.Lock() redirMux.Lock()
defer redirMux.Unlock() defer redirMux.Unlock()
@ -165,20 +170,21 @@ func ReCreateRedir(port int) error {
} }
var err error var err error
redirListener, err = redir.NewRedirProxy(addr) redirListener, err = redir.New(addr, tcpIn)
if err != nil { if err != nil {
return err return err
} }
redirUDPListener, err = redir.NewRedirUDPProxy(addr) redirUDPListener, err = tproxy.NewUDP(addr, udpIn)
if err != nil { if err != nil {
log.Warnln("Failed to start Redir UDP Listener: %s", err) log.Warnln("Failed to start Redir UDP Listener: %s", err)
} }
log.Infoln("Redirect proxy listening at: %s", redirListener.Address())
return nil return nil
} }
func ReCreateTProxy(port int) error { func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
tproxyMux.Lock() tproxyMux.Lock()
defer tproxyMux.Unlock() defer tproxyMux.Unlock()
@ -205,20 +211,21 @@ func ReCreateTProxy(port int) error {
} }
var err error var err error
tproxyListener, err = redir.NewTProxy(addr) tproxyListener, err = tproxy.New(addr, tcpIn)
if err != nil { if err != nil {
return err return err
} }
tproxyUDPListener, err = redir.NewRedirUDPProxy(addr) tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn)
if err != nil { if err != nil {
log.Warnln("Failed to start TProxy UDP Listener: %s", err) log.Warnln("Failed to start TProxy UDP Listener: %s", err)
} }
log.Infoln("TProxy server listening at: %s", tproxyListener.Address())
return nil return nil
} }
func ReCreateMixed(port int) error { func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
mixedMux.Lock() mixedMux.Lock()
defer mixedMux.Unlock() defer mixedMux.Unlock()
@ -253,17 +260,18 @@ func ReCreateMixed(port int) error {
} }
var err error var err error
mixedListener, err = mixed.NewMixedProxy(addr) mixedListener, err = mixed.New(addr, tcpIn)
if err != nil { if err != nil {
return err return err
} }
mixedUDPLister, err = socks.NewSocksUDPProxy(addr) mixedUDPLister, err = socks.NewUDP(addr, udpIn)
if err != nil { if err != nil {
mixedListener.Close() mixedListener.Close()
return err return err
} }
log.Infoln("Mixed(http+socks5) proxy listening at: %s", mixedListener.Address())
return nil return nil
} }

67
listener/mixed/mixed.go Normal file
View File

@ -0,0 +1,67 @@
package mixed
import (
"net"
"time"
"github.com/Dreamacro/clash/common/cache"
N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/http"
"github.com/Dreamacro/clash/listener/socks"
"github.com/Dreamacro/clash/transport/socks5"
)
type Listener struct {
listener net.Listener
address string
closed bool
cache *cache.Cache
}
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
ml := &Listener{l, addr, false, cache.New(30 * time.Second)}
go func() {
for {
c, err := ml.listener.Accept()
if err != nil {
if ml.closed {
break
}
continue
}
go handleConn(c, in, ml.cache)
}
}()
return ml, nil
}
func (l *Listener) Close() {
l.closed = true
l.listener.Close()
}
func (l *Listener) Address() string {
return l.address
}
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {
return
}
if head[0] == socks5.Version {
socks.HandleSocks(bufConn, in)
return
}
http.HandleConn(bufConn, in, cache)
}

56
listener/redir/tcp.go Normal file
View File

@ -0,0 +1,56 @@
package redir
import (
"net"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
)
type Listener struct {
listener net.Listener
address string
closed bool
}
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
rl := &Listener{l, addr, false}
go func() {
for {
c, err := l.Accept()
if err != nil {
if rl.closed {
break
}
continue
}
go handleRedir(c, in)
}
}()
return rl, nil
}
func (l *Listener) Close() {
l.closed = true
l.listener.Close()
}
func (l *Listener) Address() string {
return l.address
}
func handleRedir(conn net.Conn, in chan<- C.ConnContext) {
target, err := parserPacket(conn)
if err != nil {
conn.Close()
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
in <- inbound.NewSocket(target, conn, C.REDIR)
}

View File

@ -5,7 +5,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
func parserPacket(c net.Conn) (socks5.Addr, error) { func parserPacket(c net.Conn) (socks5.Addr, error) {

View File

@ -6,7 +6,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
const ( const (

View File

@ -6,7 +6,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
const ( const (

View File

@ -0,0 +1,14 @@
// +build !darwin,!linux,!freebsd
package redir
import (
"errors"
"net"
"github.com/Dreamacro/clash/transport/socks5"
)
func parserPacket(conn net.Conn) (socks5.Addr, error) {
return nil, errors.New("system not support yet")
}

View File

@ -5,29 +5,26 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
adapters "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" authStore "github.com/Dreamacro/clash/listener/auth"
authStore "github.com/Dreamacro/clash/proxy/auth" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/tunnel"
) )
type SockListener struct { type Listener struct {
net.Listener listener net.Listener
address string address string
closed bool closed bool
} }
func NewSocksProxy(addr string) (*SockListener, error) { func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sl := &SockListener{l, addr, false} sl := &Listener{l, addr, false}
go func() { go func() {
log.Infoln("SOCKS proxy listening at: %s", addr)
for { for {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
@ -36,23 +33,23 @@ func NewSocksProxy(addr string) (*SockListener, error) {
} }
continue continue
} }
go HandleSocks(c) go HandleSocks(c, in)
} }
}() }()
return sl, nil return sl, nil
} }
func (l *SockListener) Close() { func (l *Listener) Close() {
l.closed = true l.closed = true
l.Listener.Close() l.listener.Close()
} }
func (l *SockListener) Address() string { func (l *Listener) Address() string {
return l.address return l.address
} }
func HandleSocks(conn net.Conn) { func HandleSocks(conn net.Conn, in chan<- C.ConnContext) {
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
if err != nil { if err != nil {
conn.Close() conn.Close()
@ -66,5 +63,5 @@ func HandleSocks(conn net.Conn) {
io.Copy(ioutil.Discard, conn) io.Copy(ioutil.Discard, conn)
return return
} }
tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS)) in <- inbound.NewSocket(target, conn, C.SOCKS)
} }

View File

@ -3,33 +3,31 @@ package socks
import ( import (
"net" "net"
adapters "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/sockopt" "github.com/Dreamacro/clash/common/sockopt"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/transport/socks5"
) )
type SockUDPListener struct { type UDPListener struct {
net.PacketConn packetConn net.PacketConn
address string address string
closed bool closed bool
} }
func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
l, err := net.ListenPacket("udp", addr) l, err := net.ListenPacket("udp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = sockopt.UDPReuseaddr(l.(*net.UDPConn)) if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil {
if err != nil {
log.Warnln("Failed to Reuse UDP Address: %s", err) log.Warnln("Failed to Reuse UDP Address: %s", err)
} }
sl := &SockUDPListener{l, addr, false} sl := &UDPListener{l, addr, false}
go func() { go func() {
for { for {
buf := pool.Get(pool.RelayBufferSize) buf := pool.Get(pool.RelayBufferSize)
@ -41,23 +39,23 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
} }
continue continue
} }
handleSocksUDP(l, buf[:n], remoteAddr) handleSocksUDP(l, in, buf[:n], remoteAddr)
} }
}() }()
return sl, nil return sl, nil
} }
func (l *SockUDPListener) Close() error { func (l *UDPListener) Close() error {
l.closed = true l.closed = true
return l.PacketConn.Close() return l.packetConn.Close()
} }
func (l *SockUDPListener) Address() string { func (l *UDPListener) Address() string {
return l.address return l.address
} }
func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
target, payload, err := socks5.DecodeUDPPacket(buf) target, payload, err := socks5.DecodeUDPPacket(buf)
if err != nil { if err != nil {
// Unresolved UDP packet, return buffer to the pool // Unresolved UDP packet, return buffer to the pool
@ -70,5 +68,8 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
payload: payload, payload: payload,
bufRef: buf, bufRef: buf,
} }
tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) select {
case in <- inbound.NewPacket(target, packet, C.TPROXY):
default:
}
} }

View File

@ -4,7 +4,7 @@ import (
"net" "net"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
type packet struct { type packet struct {

View File

@ -1,4 +1,4 @@
package redir package tproxy
import ( import (
"net" "net"

View File

@ -1,6 +1,6 @@
// +build linux // +build linux
package redir package tproxy
import ( import (
"net" "net"

View File

@ -1,6 +1,6 @@
// +build !linux // +build !linux
package redir package tproxy
import ( import (
"errors" "errors"

68
listener/tproxy/tproxy.go Normal file
View File

@ -0,0 +1,68 @@
package tproxy
import (
"net"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
type Listener struct {
listener net.Listener
address string
closed bool
}
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
tl := l.(*net.TCPListener)
rc, err := tl.SyscallConn()
if err != nil {
return nil, err
}
err = setsockopt(rc, addr)
if err != nil {
return nil, err
}
rl := &Listener{
listener: l,
address: addr,
}
go func() {
for {
c, err := l.Accept()
if err != nil {
if rl.closed {
break
}
continue
}
go rl.handleTProxy(c, in)
}
}()
return rl, nil
}
func (l *Listener) Close() {
l.closed = true
l.listener.Close()
}
func (l *Listener) Address() string {
return l.address
}
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
conn.(*net.TCPConn).SetKeepAlive(true)
in <- inbound.NewSocket(target, conn, C.TPROXY)
}

View File

@ -1,28 +1,27 @@
package redir package tproxy
import ( import (
"net" "net"
adapters "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/transport/socks5"
) )
type RedirUDPListener struct { type UDPListener struct {
net.PacketConn packetConn net.PacketConn
address string address string
closed bool closed bool
} }
func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
l, err := net.ListenPacket("udp", addr) l, err := net.ListenPacket("udp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rl := &RedirUDPListener{l, addr, false} rl := &UDPListener{l, addr, false}
c := l.(*net.UDPConn) c := l.(*net.UDPConn)
@ -53,27 +52,30 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
if err != nil { if err != nil {
continue continue
} }
handleRedirUDP(l, buf[:n], lAddr, rAddr) handlePacketConn(l, in, buf[:n], lAddr, rAddr)
} }
}() }()
return rl, nil return rl, nil
} }
func (l *RedirUDPListener) Close() error { func (l *UDPListener) Close() error {
l.closed = true l.closed = true
return l.PacketConn.Close() return l.packetConn.Close()
} }
func (l *RedirUDPListener) Address() string { func (l *UDPListener) Address() string {
return l.address return l.address
} }
func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
target := socks5.ParseAddrToSocksAddr(rAddr) target := socks5.ParseAddrToSocksAddr(rAddr)
pkt := &packet{ pkt := &packet{
lAddr: lAddr, lAddr: lAddr,
buf: buf, buf: buf,
} }
tunnel.AddPacket(adapters.NewPacket(target, pkt, C.TPROXY)) select {
case in <- inbound.NewPacket(target, pkt, C.TPROXY):
default:
}
} }

View File

@ -1,8 +1,10 @@
// +build linux // +build linux
package redir package tproxy
import ( import (
"encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"os" "os"
@ -10,6 +12,11 @@ import (
"syscall" "syscall"
) )
const (
IPV6_TRANSPARENT = 0x4b
IPV6_RECVORIGDSTADDR = 0x4a
)
// dialUDP acts like net.DialUDP for transparent proxy. // dialUDP acts like net.DialUDP for transparent proxy.
// It binds to a non-local address(`lAddr`). // It binds to a non-local address(`lAddr`).
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
@ -94,3 +101,24 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
} }
return syscall.AF_INET6 return syscall.AF_INET6
} }
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return nil, err
}
for _, msg := range msgs {
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
ip := net.IP(msg.Data[4:8])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
ip := net.IP(msg.Data[8:24])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
}
}
return nil, errors.New("cannot find origDst")
}

View File

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

View File

@ -1,114 +0,0 @@
package http
import (
"bufio"
"encoding/base64"
"net"
"net/http"
"strings"
"time"
adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/log"
authStore "github.com/Dreamacro/clash/proxy/auth"
"github.com/Dreamacro/clash/tunnel"
)
type HTTPListener struct {
net.Listener
address string
closed bool
cache *cache.Cache
}
func NewHTTPProxy(addr string) (*HTTPListener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
hl := &HTTPListener{l, addr, false, cache.New(30 * time.Second)}
go func() {
log.Infoln("HTTP proxy listening at: %s", addr)
for {
c, err := hl.Accept()
if err != nil {
if hl.closed {
break
}
continue
}
go HandleConn(c, hl.cache)
}
}()
return hl, nil
}
func (l *HTTPListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *HTTPListener) Address() string {
return l.address
}
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
if result := cache.Get(loginStr); result != nil {
ret = result.(bool)
return
}
loginData, err := base64.StdEncoding.DecodeString(loginStr)
login := strings.Split(string(loginData), ":")
ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1])
cache.Put(loginStr, ret, time.Minute)
return
}
func HandleConn(conn net.Conn, cache *cache.Cache) {
br := bufio.NewReader(conn)
keepAlive:
request, err := http.ReadRequest(br)
if err != nil || request.URL.Host == "" {
conn.Close()
return
}
keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
authenticator := authStore.Authenticator()
if authenticator != nil {
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
if keepAlive {
goto keepAlive
}
return
} else if !canActivate(authStrings[1], authenticator, cache) {
conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n"))
log.Infoln("Auth failed from %s", conn.RemoteAddr().String())
if keepAlive {
goto keepAlive
}
conn.Close()
return
}
}
if request.Method == http.MethodConnect {
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil {
conn.Close()
return
}
tunnel.Add(adapters.NewHTTPS(request, conn))
return
}
tunnel.Add(adapters.NewHTTP(request, conn))
}

View File

@ -1,69 +0,0 @@
package mixed
import (
"net"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/socks5"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/socks"
)
type MixedListener struct {
net.Listener
address string
closed bool
cache *cache.Cache
}
func NewMixedProxy(addr string) (*MixedListener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)}
go func() {
log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr)
for {
c, err := ml.Accept()
if err != nil {
if ml.closed {
break
}
continue
}
go handleConn(c, ml.cache)
}
}()
return ml, nil
}
func (l *MixedListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *MixedListener) Address() string {
return l.address
}
func handleConn(conn net.Conn, cache *cache.Cache) {
bufConn := NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {
return
}
if head[0] == socks5.Version {
socks.HandleSocks(bufConn)
return
}
http.HandleConn(bufConn, cache)
}

View File

@ -1,59 +0,0 @@
package redir
import (
"net"
"github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
)
type RedirListener struct {
net.Listener
address string
closed bool
}
func NewRedirProxy(addr string) (*RedirListener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
rl := &RedirListener{l, addr, false}
go func() {
log.Infoln("Redir proxy listening at: %s", addr)
for {
c, err := l.Accept()
if err != nil {
if rl.closed {
break
}
continue
}
go handleRedir(c)
}
}()
return rl, nil
}
func (l *RedirListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *RedirListener) Address() string {
return l.address
}
func handleRedir(conn net.Conn) {
target, err := parserPacket(conn)
if err != nil {
conn.Close()
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR))
}

View File

@ -1,12 +0,0 @@
package redir
import (
"errors"
"net"
"github.com/Dreamacro/clash/component/socks5"
)
func parserPacket(conn net.Conn) (socks5.Addr, error) {
return nil, errors.New("Windows not support yet")
}

View File

@ -1,71 +0,0 @@
package redir
import (
"net"
"github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
)
type TProxyListener struct {
net.Listener
address string
closed bool
}
func NewTProxy(addr string) (*TProxyListener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
tl := l.(*net.TCPListener)
rc, err := tl.SyscallConn()
if err != nil {
return nil, err
}
err = setsockopt(rc, addr)
if err != nil {
return nil, err
}
rl := &TProxyListener{
Listener: l,
address: addr,
}
go func() {
log.Infoln("TProxy server listening at: %s", addr)
for {
c, err := l.Accept()
if err != nil {
if rl.closed {
break
}
continue
}
go rl.handleRedir(c)
}
}()
return rl, nil
}
func (l *TProxyListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *TProxyListener) Address() string {
return l.address
}
func (l *TProxyListener) handleRedir(conn net.Conn) {
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
conn.(*net.TCPConn).SetKeepAlive(true)
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))
}

View File

@ -1,36 +0,0 @@
// +build linux
package redir
import (
"encoding/binary"
"errors"
"net"
"syscall"
)
const (
IPV6_TRANSPARENT = 0x4b
IPV6_RECVORIGDSTADDR = 0x4a
)
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return nil, err
}
for _, msg := range msgs {
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
ip := net.IP(msg.Data[4:8])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
ip := net.IP(msg.Data[8:24])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
}
}
return nil, errors.New("cannot find origDst")
}

View File

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

49
test/README.md Normal file
View File

@ -0,0 +1,49 @@
## Clash testing suit
### Protocol testing suit
* TCP pingpong test
* UDP pingpong test
* TCP large data test
* UDP large data test
### Protocols
- [x] Shadowsocks
- [x] Normal
- [x] ObfsHTTP
- [x] ObfsTLS
- [x] ObfsV2rayPlugin
- [x] Vmess
- [x] Normal
- [x] AEAD
- [x] HTTP
- [x] HTTP2
- [x] TLS
- [x] Websocket
- [x] Websocket TLS
- [x] gRPC
- [x] Trojan
- [x] Normal
- [x] gRPC
- [x] Snell
- [x] Normal
- [x] ObfsHTTP
- [x] ObfsTLS
### Features
- [ ] DNS
- [x] DNS Server
- [x] FakeIP
- [x] Host
### Command
Prerequisite
* docker (support Linux and macOS)
```
$ go test -p 1 -v
```

635
test/clash_test.go Normal file
View File

@ -0,0 +1,635 @@
package main
import (
"context"
"crypto/md5"
"crypto/rand"
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"time"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
)
const (
ImageShadowsocks = "mritd/shadowsocks:latest"
ImageVmess = "v2fly/v2fly-core:latest"
ImageTrojan = "trojangfw/trojan:latest"
ImageSnell = "icpz/snell-server:latest"
ImageXray = "teddysun/xray:latest"
)
var (
waitTime = time.Second
localIP = net.ParseIP("127.0.0.1")
defaultExposedPorts = nat.PortSet{
"10002/tcp": struct{}{},
"10002/udp": struct{}{},
}
defaultPortBindings = nat.PortMap{
"10002/tcp": []nat.PortBinding{
{HostPort: "10002", HostIP: "0.0.0.0"},
},
"10002/udp": []nat.PortBinding{
{HostPort: "10002", HostIP: "0.0.0.0"},
},
}
)
func init() {
if runtime.GOOS == "darwin" {
isDarwin = true
}
currentDir, err := os.Getwd()
if err != nil {
panic(err)
}
homeDir := filepath.Join(currentDir, "config")
C.SetHomeDir(homeDir)
if isDarwin {
localIP, err = defaultRouteIP()
if err != nil {
panic(err)
}
}
c, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
defer c.Close()
list, err := c.ImageList(context.Background(), types.ImageListOptions{All: true})
if err != nil {
panic(err)
}
imageExist := func(image string) bool {
for _, item := range list {
for _, tag := range item.RepoTags {
if image == tag {
return true
}
}
}
return false
}
images := []string{
ImageShadowsocks,
ImageVmess,
ImageTrojan,
ImageSnell,
ImageXray,
}
for _, image := range images {
if imageExist(image) {
continue
}
imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{})
if err != nil {
panic(err)
}
io.Copy(io.Discard, imageStream)
}
}
var clean = `
port: 0
socks-port: 0
mixed-port: 0
redir-port: 0
tproxy-port: 0
dns:
enable: false
`
func cleanup() {
parseAndApply(clean)
}
func parseAndApply(cfgStr string) error {
cfg, err := executor.ParseWithBytes([]byte(cfgStr))
if err != nil {
return err
}
executor.ApplyConfig(cfg, true)
return nil
}
func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {
pingCh := make(chan []byte)
pongCh := make(chan []byte)
test := func(t *testing.T) error {
defer close(pingCh)
defer close(pongCh)
pingOpen := false
pongOpen := false
var recv []byte
for {
if pingOpen && pongOpen {
break
}
select {
case recv, pingOpen = <-pingCh:
assert.True(t, pingOpen)
assert.Equal(t, []byte("ping"), recv)
case recv, pongOpen = <-pongCh:
assert.True(t, pongOpen)
assert.Equal(t, []byte("pong"), recv)
case <-time.After(10 * time.Second):
return errors.New("timeout")
}
}
return nil
}
return pingCh, pongCh, test
}
func newLargeDataPair() (chan hashPair, chan hashPair, func(t *testing.T) error) {
pingCh := make(chan hashPair)
pongCh := make(chan hashPair)
test := func(t *testing.T) error {
defer close(pingCh)
defer close(pongCh)
pingOpen := false
pongOpen := false
var serverPair hashPair
var clientPair hashPair
for {
if pingOpen && pongOpen {
break
}
select {
case serverPair, pingOpen = <-pingCh:
assert.True(t, pingOpen)
case clientPair, pongOpen = <-pongCh:
assert.True(t, pongOpen)
case <-time.After(10 * time.Second):
return errors.New("timeout")
}
}
assert.Equal(t, serverPair.recvHash, clientPair.sendHash)
assert.Equal(t, serverPair.sendHash, clientPair.recvHash)
return nil
}
return pingCh, pongCh, test
}
func testPingPongWithSocksPort(t *testing.T, port int) {
pingCh, pongCh, test := newPingPongPair()
go func() {
l, err := net.Listen("tcp", ":10001")
if err != nil {
assert.FailNow(t, err.Error())
}
c, err := l.Accept()
if err != nil {
assert.FailNow(t, err.Error())
}
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
assert.FailNow(t, err.Error())
}
pingCh <- buf
if _, err := c.Write([]byte("pong")); err != nil {
assert.FailNow(t, err.Error())
}
l.Close()
}()
go func() {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
assert.FailNow(t, err.Error())
}
if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil {
assert.FailNow(t, err.Error())
}
if _, err := c.Write([]byte("ping")); err != nil {
assert.FailNow(t, err.Error())
}
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
assert.FailNow(t, err.Error())
}
pongCh <- buf
c.Close()
}()
test(t)
}
func testPingPongWithConn(t *testing.T, c net.Conn) error {
l, err := net.Listen("tcp", ":10001")
if err != nil {
return err
}
defer l.Close()
pingCh, pongCh, test := newPingPongPair()
go func() {
c, err := l.Accept()
if err != nil {
return
}
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
return
}
pingCh <- buf
if _, err := c.Write([]byte("pong")); err != nil {
return
}
}()
go func() {
if _, err := c.Write([]byte("ping")); err != nil {
return
}
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
return
}
pongCh <- buf
}()
return test(t)
}
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
l, err := net.ListenPacket("udp", ":10001")
if err != nil {
return err
}
defer l.Close()
rAddr := &net.UDPAddr{IP: localIP, Port: 10001}
pingCh, pongCh, test := newPingPongPair()
go func() {
buf := make([]byte, 1024)
n, rAddr, err := l.ReadFrom(buf)
if err != nil {
return
}
pingCh <- buf[:n]
if _, err := l.WriteTo([]byte("pong"), rAddr); err != nil {
return
}
}()
go func() {
if _, err := pc.WriteTo([]byte("ping"), rAddr); err != nil {
return
}
buf := make([]byte, 1024)
n, _, err := pc.ReadFrom(buf)
if err != nil {
return
}
pongCh <- buf[:n]
}()
return test(t)
}
type hashPair struct {
sendHash map[int][]byte
recvHash map[int][]byte
}
func testLargeDataWithConn(t *testing.T, c net.Conn) error {
l, err := net.Listen("tcp", ":10001")
if err != nil {
return err
}
defer l.Close()
times := 100
chunkSize := int64(64 * 1024)
pingCh, pongCh, test := newLargeDataPair()
writeRandData := func(conn net.Conn) (map[int][]byte, error) {
buf := make([]byte, chunkSize)
hashMap := map[int][]byte{}
for i := 0; i < times; i++ {
if _, err := rand.Read(buf[1:]); err != nil {
return nil, err
}
buf[0] = byte(i)
hash := md5.Sum(buf)
hashMap[i] = hash[:]
if _, err := conn.Write(buf); err != nil {
return nil, err
}
}
return hashMap, nil
}
go func() {
c, err := l.Accept()
if err != nil {
return
}
hashMap := map[int][]byte{}
buf := make([]byte, chunkSize)
for i := 0; i < times; i++ {
_, err := io.ReadFull(c, buf)
if err != nil {
t.Log(err.Error())
return
}
hash := md5.Sum(buf)
hashMap[int(buf[0])] = hash[:]
}
sendHash, err := writeRandData(c)
if err != nil {
t.Log(err.Error())
return
}
pingCh <- hashPair{
sendHash: sendHash,
recvHash: hashMap,
}
}()
go func() {
sendHash, err := writeRandData(c)
if err != nil {
t.Log(err.Error())
return
}
hashMap := map[int][]byte{}
buf := make([]byte, chunkSize)
for i := 0; i < times; i++ {
_, err := io.ReadFull(c, buf)
if err != nil {
t.Log(err.Error())
return
}
hash := md5.Sum(buf)
hashMap[int(buf[0])] = hash[:]
}
pongCh <- hashPair{
sendHash: sendHash,
recvHash: hashMap,
}
}()
return test(t)
}
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
l, err := net.ListenPacket("udp", ":10001")
if err != nil {
return err
}
defer l.Close()
rAddr := &net.UDPAddr{IP: localIP, Port: 10001}
times := 50
chunkSize := int64(1024)
pingCh, pongCh, test := newLargeDataPair()
writeRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) {
hashMap := map[int][]byte{}
mux := sync.Mutex{}
for i := 0; i < times; i++ {
go func(idx int) {
buf := make([]byte, chunkSize)
if _, err := rand.Read(buf[1:]); err != nil {
t.Log(err.Error())
return
}
buf[0] = byte(idx)
hash := md5.Sum(buf)
mux.Lock()
hashMap[idx] = hash[:]
mux.Unlock()
if _, err := pc.WriteTo(buf, addr); err != nil {
t.Log(err.Error())
return
}
}(i)
}
return hashMap, nil
}
go func() {
var rAddr net.Addr
hashMap := map[int][]byte{}
buf := make([]byte, 64*1024)
for i := 0; i < times; i++ {
_, rAddr, err = l.ReadFrom(buf)
if err != nil {
t.Log(err.Error())
return
}
hash := md5.Sum(buf[:chunkSize])
hashMap[int(buf[0])] = hash[:]
}
sendHash, err := writeRandData(l, rAddr)
if err != nil {
t.Log(err.Error())
return
}
pingCh <- hashPair{
sendHash: sendHash,
recvHash: hashMap,
}
}()
go func() {
sendHash, err := writeRandData(pc, rAddr)
if err != nil {
t.Log(err.Error())
return
}
hashMap := map[int][]byte{}
buf := make([]byte, 64*1024)
for i := 0; i < times; i++ {
_, _, err := pc.ReadFrom(buf)
if err != nil {
t.Log(err.Error())
return
}
hash := md5.Sum(buf[:chunkSize])
hashMap[int(buf[0])] = hash[:]
}
pongCh <- hashPair{
sendHash: sendHash,
recvHash: hashMap,
}
}()
return test(t)
}
func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
assert.NoError(t, err)
errCh := make(chan error, 1)
go func() {
buf := make([]byte, 1024)
_, _, err := pc.ReadFrom(buf)
errCh <- err
}()
select {
case <-errCh:
return nil
case <-time.After(time.Second * 10):
return errors.New("timeout")
}
}
func testSuit(t *testing.T, proxy C.ProxyAdapter) {
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
if err != nil {
assert.FailNow(t, err.Error())
}
defer conn.Close()
assert.NoError(t, testPingPongWithConn(t, conn))
conn, err = proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
if err != nil {
assert.FailNow(t, err.Error())
}
defer conn.Close()
assert.NoError(t, testLargeDataWithConn(t, conn))
if !proxy.SupportUDP() {
return
}
pc, err := proxy.DialUDP(&C.Metadata{
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
if err != nil {
assert.FailNow(t, err.Error())
}
defer pc.Close()
assert.NoError(t, testPingPongWithPacketConn(t, pc))
pc, err = proxy.DialUDP(&C.Metadata{
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
if err != nil {
assert.FailNow(t, err.Error())
}
defer pc.Close()
assert.NoError(t, testLargeDataWithPacketConn(t, pc))
pc, err = proxy.DialUDP(&C.Metadata{
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
if err != nil {
assert.FailNow(t, err.Error())
}
defer pc.Close()
assert.NoError(t, testPacketConnTimeout(t, pc))
}
func TestClash_Basic(t *testing.T) {
basic := `
mixed-port: 10000
log-level: silent
`
if err := parseAndApply(basic); err != nil {
assert.FailNow(t, err.Error())
}
defer cleanup()
time.Sleep(waitTime)
testPingPongWithSocksPort(t, 10000)
}

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5
5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo
PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE
sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R
i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5
LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge
gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+
y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO
jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z
ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv
H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG
o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ
CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49
aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33
bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7
Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh
ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO
pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT
4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi
GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA
vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB
fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z
zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X
DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28
9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP
XCar+uxMBXI1zbXqd9QdEwy4Ig==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB
hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh
bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl
cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz
MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl
bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy
by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8
3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI
YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b
IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo
UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2
I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG
A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln
F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP
TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa
Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1
Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz
Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG
WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6
AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6
gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS
tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w=
-----END CERTIFICATE-----

View File

@ -0,0 +1,4 @@
[snell-server]
listen = 0.0.0.0:10002
psk = password
obfs = http

Some files were not shown because too many files have changed in this diff Show More