Compare commits

..

40 Commits

Author SHA1 Message Date
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
baf03b81e3 Fix: remove unused function 2021-04-08 22:27:41 +08:00
9807e1189c Chore: update dependencies 2021-04-08 22:15:30 +08:00
3d5a0d9f73 Fix: trojan/vmess grpc broken 2021-04-07 22:57:46 +08:00
cc96187f58 Fix: trojan grpc udp broken 2021-04-05 23:26:13 +08:00
3aefa1d924 Chore: some chores 2021-04-05 13:31:10 +08:00
42e21b3733 Chore: refine go import 2021-04-05 13:00:49 +08:00
0a35237915 Fix: should reset fast node when tolerance enable and not alive on url-test group 2021-04-04 17:40:25 +08:00
a1f3a5ea26 Chore: -v add golang version 2021-04-04 17:36:22 +08:00
e63f995258 Chore: update dependencies (#1331) 2021-04-03 14:59:03 +08:00
d0c829c578 Fix: domain dns should follow hosts config, close #1318 2021-04-01 21:20:44 +08:00
4ad9761b32 Fix: don't resolve AAAA record when ipv6 is false and use go dns resolver 2021-04-01 18:03:30 +08:00
1f593d37fb Chore: use mixed-port instead of port when initial config (#1319) 2021-04-01 15:35:33 +08:00
109bfcb0f9 Feature: add vmess aead header support 2021-03-30 17:34:16 +08:00
7ee49f5171 Fix: HTTP server should close when Connection is close 2021-03-30 16:33:49 +08:00
d759d16944 Style: cleanup code 2021-03-24 01:00:21 +08:00
807d53c1e7 Chore: Clarify the definition of StreamConn and DialContext 2021-03-22 23:26:20 +08:00
1355196b7c Fix: grpc connection panic 2021-03-18 23:19:00 +08:00
573316bcde Feature: add gRPC Transport for vmess/trojan (#1287)
Co-authored-by: eMeab <32988354+eMeab@users.noreply.github.com>
Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com>
2021-03-18 19:40:34 +08:00
784c28266c Fix: vmess http broken 2021-03-18 17:11:10 +08:00
5da1b2a8aa Fix: set metadata.AddrType if host is ip string after remove host (#1291) 2021-03-12 17:41:37 +08:00
0976d27cb1 Fix: github actions remove prerelease option 2021-03-10 21:22:22 +08:00
63 changed files with 978 additions and 222 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

@ -44,4 +44,3 @@ jobs:
with:
files: bin/*
draft: true
prerelease: true

View File

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

View File

@ -57,4 +57,3 @@ This software is released under the GPL-3.0 license.
- [x] Redir proxy
- [x] UDP support
- [x] Connection manager
- [ ] ~~Event API~~

View File

@ -9,7 +9,7 @@ import (
"github.com/Dreamacro/clash/context"
)
// NewHTTP recieve normal http request and return HTTPContext
// NewHTTP receive normal http request and return HTTPContext
func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTP

View File

@ -8,7 +8,7 @@ import (
"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 {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT

View File

@ -8,7 +8,7 @@ import (
"github.com/Dreamacro/clash/context"
)
// 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 {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP

View File

@ -21,36 +21,44 @@ type Base struct {
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
}
@ -64,10 +72,12 @@ type conn struct {
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())
}
@ -81,10 +91,12 @@ type packetConn struct {
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())
}
@ -99,16 +111,19 @@ type Proxy struct {
alive *atomic.Bool
}
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil {
@ -117,6 +132,7 @@ func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return conn, err
}
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
@ -127,6 +143,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
}
// 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) {
var max uint16 = 0xffff
if !p.alive.Load() {
@ -144,6 +161,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
return history.Delay
}
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
@ -158,6 +176,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
}
// URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() {
p.alive.Store(err == nil)

View File

@ -12,6 +12,7 @@ type Direct struct {
*Base
}
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
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
}
// DialUDP implements C.ProxyAdapter
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {

View File

@ -35,6 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig)
@ -51,13 +52,16 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, nil
}
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = h.StreamConn(c, metadata)
if err != nil {
return nil, err

View File

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

View File

@ -54,6 +54,7 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch ss.obfsMode {
case "tls":
@ -73,17 +74,21 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
return c, err
}
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata)
return NewConn(c, ss), err
}
// DialUDP implements C.ProxyAdapter
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
@ -92,6 +97,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil {
pc.Close()
return nil, err
}
@ -99,6 +105,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
}
// MarshalJSON implements C.ProxyAdapter
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),

View File

@ -11,6 +11,7 @@ import (
"github.com/Dreamacro/clash/component/ssr/obfs"
"github.com/Dreamacro/clash/component/ssr/protocol"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
@ -36,6 +37,7 @@ type ShadowSocksROption struct {
UDP bool `proxy:"udp,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c)
@ -57,17 +59,21 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
return c, err
}
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ssr.StreamConn(c, metadata)
return NewConn(c, ssr), err
}
// DialUDP implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
@ -76,6 +82,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
addr, err := resolveUDPAddr("udp", ssr.addr)
if err != nil {
pc.Close()
return nil, err
}
@ -84,6 +91,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
}
// MarshalJSON implements C.ProxyAdapter
func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ssr.Type().String(),

View File

@ -48,6 +48,7 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
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) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
port, _ := strconv.Atoi(metadata.DstPort)
@ -55,7 +56,8 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, err
}
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
if s.version == snell.Version2 {
c, err := s.pool.Get()
if err != nil {
@ -63,7 +65,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
}
port, _ := strconv.Atoi(metadata.DstPort)
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version)
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
c.Close()
return nil, err
}
return NewConn(c, s), err
}
@ -73,6 +78,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = s.StreamConn(c, metadata)
return NewConn(c, s), err
}

View File

@ -35,6 +35,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if ss.tls {
cc := tls.Client(c, ss.tlsConfig)
@ -58,13 +59,16 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, nil
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata)
if err != nil {
return nil, err
@ -73,6 +77,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
return NewConn(c, ss), nil
}
// DialUDP implements C.ProxyAdapter
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
@ -88,11 +93,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
c = cc
}
defer func() {
if err != nil {
c.Close()
}
}()
defer safeConnClose(c, err)
tcpKeepAlive(c)
var user *socks5.User

View File

@ -2,34 +2,52 @@ package outbound
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/gun"
"github.com/Dreamacro/clash/component/trojan"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/http2"
)
type Trojan struct {
*Base
instance *trojan.Trojan
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *http2.Transport
}
type TrojanOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err := t.instance.StreamConn(c)
var err error
if t.transport != nil {
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
} else {
c, err = t.instance.StreamConn(c)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
@ -38,12 +56,31 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, err
}
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
// gun transport
if t.transport != nil {
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close()
return nil, err
}
return NewConn(c, t), nil
}
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = t.StreamConn(c, metadata)
if err != nil {
return nil, err
@ -52,17 +89,30 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return NewConn(c, t), err
}
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
c, err = t.instance.StreamConn(c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
// DialUDP implements C.ProxyAdapter
func (t *Trojan) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
var c net.Conn
// grpc transport
if t.transport != nil {
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
defer safeConnClose(c, err)
} else {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err = dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
defer safeConnClose(c, err)
tcpKeepAlive(c)
c, err = t.instance.StreamConn(c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
@ -95,7 +145,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tOption.ServerName = option.SNI
}
return &Trojan{
t := &Trojan{
Base: &Base{
name: option.Name,
addr: addr,
@ -103,5 +153,33 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
udp: option.UDP,
},
instance: trojan.New(tOption),
}, nil
}
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
c, err := dialer.DialContext(context.Background(), "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
}
tcpKeepAlive(c)
return c, nil
}
tlsConfig := &tls.Config{
NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: tOption.SkipCertVerify,
ServerName: tOption.ServerName,
ClientSessionCache: getClientSessionCache(),
}
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
Host: tOption.ServerName,
}
}
return t, nil
}

View File

@ -98,3 +98,9 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}
func safeConnClose(c net.Conn, err error) {
if err != nil {
c.Close()
}
}

View File

@ -2,6 +2,7 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
@ -10,15 +11,23 @@ import (
"strings"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/gun"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/http2"
)
type Vmess struct {
*Base
client *vmess.Client
option *VmessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *http2.Transport
}
type VmessOption struct {
@ -33,6 +42,7 @@ type VmessOption struct {
Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
@ -50,6 +60,11 @@ type HTTP2Options struct {
Path string `proxy:"path,omitempty"`
}
type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
switch v.option.Network {
@ -129,6 +144,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
}
c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
default:
// handle TLS
if v.option.TLS {
@ -154,19 +171,38 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return v.client.StreamConn(c, parseVmessAddr(metadata))
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
// gun transport
if v.transport != nil {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
return NewConn(c, v), err
}
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
// DialUDP implements C.ProxyAdapter
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
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
@ -175,17 +211,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
metadata.DstIP = ip
}
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
var c net.Conn
// gun transport
if v.transport != nil {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
} else {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err = dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
}
tcpKeepAlive(c)
c, err = v.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
@ -197,15 +249,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
Security: security,
HostName: option.Server,
Port: strconv.Itoa(option.Port),
IsAead: option.AlterID == 0,
})
if err != nil {
return nil, err
}
if option.Network == "h2" && !option.TLS {
return nil, fmt.Errorf("TLS must be true with h2 network")
switch option.Network {
case "h2", "grpc":
if !option.TLS {
return nil, fmt.Errorf("TLS must be true with h2/grpc network")
}
}
return &Vmess{
v := &Vmess{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
@ -214,7 +271,44 @@ func NewVmess(option VmessOption) (*Vmess, error) {
},
client: client,
option: &option,
}, nil
}
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) {
c, err := dialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
return c, nil
}
gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName,
}
tlsConfig := &tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
}
if v.option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host
gunConfig.Host = host
}
v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
return v, nil
}
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {

View File

@ -22,6 +22,7 @@ func (f *Fallback) Now() string {
return proxy.Name()
}
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy(true)
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
}
// DialUDP implements C.ProxyAdapter
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy(true)
pc, err := proxy.DialUDP(metadata)
@ -40,6 +42,7 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool {
if f.disableUDP {
return false
@ -49,6 +52,7 @@ func (f *Fallback) SupportUDP() bool {
return proxy.SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
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 {
proxy := f.findAliveProxy(true)
return proxy

View File

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

View File

@ -19,6 +19,7 @@ type Relay struct {
providers []provider.ProxyProvider
}
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxies := r.proxies(metadata, true)
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
}
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range r.rawProxies(false) {

View File

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

View File

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

View File

@ -16,25 +16,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
}
ip := net.ParseIP(host)
if ip != nil {
if ip.To4() != nil {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip,
DstPort: port,
}
return
} else {
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
} else {
if ip == nil {
addr = &C.Metadata{
AddrType: C.AtypDomainName,
Host: host,
@ -42,7 +24,23 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
DstPort: port,
}
return
} else if ip4 := ip.To4(); ip4 != nil {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip4,
DstPort: port,
}
return
}
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
func tcpKeepAlive(c net.Conn) {

View File

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

View File

@ -40,7 +40,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
return nil, err
}
var hcInterval uint = 0
var hcInterval uint
if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval)
}

239
component/gun/gun.go Normal file
View File

@ -0,0 +1,239 @@
// Modified from: https://github.com/Qv2ray/gun-lite
// License: MIT
package gun
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"sync"
"time"
"go.uber.org/atomic"
"golang.org/x/net/http2"
)
var (
ErrInvalidLength = errors.New("invalid length")
ErrSmallBuffer = errors.New("buffer too small")
)
var (
defaultHeader = http.Header{
"content-type": []string{"application/grpc"},
"user-agent": []string{"grpc-go/1.36.0"},
}
bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
)
type DialFn = func(network, addr string) (net.Conn, error)
type Conn struct {
response *http.Response
request *http.Request
transport *http2.Transport
writer *io.PipeWriter
once sync.Once
close *atomic.Bool
err error
remain int
br *bufio.Reader
// deadlines
deadline *time.Timer
}
type Config struct {
ServiceName string
Host string
}
func (g *Conn) initRequest() {
response, err := g.transport.RoundTrip(g.request)
if err != nil {
g.err = err
g.writer.Close()
return
}
if !g.close.Load() {
g.response = response
g.br = bufio.NewReader(response.Body)
} else {
response.Body.Close()
}
}
func (g *Conn) Read(b []byte) (n int, err error) {
g.once.Do(g.initRequest)
if g.err != nil {
return 0, g.err
}
if g.remain > 0 {
size := g.remain
if len(b) < size {
size = len(b)
}
n, err = io.ReadFull(g.br, b[:size])
g.remain -= n
return
} else if g.response == nil {
return 0, net.ErrClosed
}
// 0x00 grpclength(uint32) 0x0A uleb128 payload
_, err = g.br.Discard(6)
if err != nil {
return 0, err
}
protobufPayloadLen, err := binary.ReadUvarint(g.br)
if err != nil {
return 0, ErrInvalidLength
}
size := int(protobufPayloadLen)
if len(b) < size {
size = len(b)
}
n, err = io.ReadFull(g.br, b[:size])
if err != nil {
return
}
remain := int(protobufPayloadLen) - n
if remain > 0 {
g.remain = remain
}
return n, nil
}
func (g *Conn) Write(b []byte) (n int, err error) {
protobufHeader := [binary.MaxVarintLen64 + 1]byte{0x0A}
varuintSize := binary.PutUvarint(protobufHeader[1:], uint64(len(b)))
grpcHeader := make([]byte, 5)
grpcPayloadLen := uint32(varuintSize + 1 + len(b))
binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen)
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
defer buf.Reset()
buf.Write(grpcHeader)
buf.Write(protobufHeader[:varuintSize+1])
buf.Write(b)
_, err = g.writer.Write(buf.Bytes())
if err == io.ErrClosedPipe && g.err != nil {
err = g.err
}
return len(b), err
}
func (g *Conn) Close() error {
g.close.Store(true)
if r := g.response; r != nil {
r.Body.Close()
}
return g.writer.Close()
}
func (g *Conn) LocalAddr() net.Addr { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} }
func (g *Conn) RemoteAddr() net.Addr { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} }
func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) }
func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) }
func (g *Conn) SetDeadline(t time.Time) error {
d := time.Until(t)
if g.deadline != nil {
g.deadline.Reset(d)
return nil
}
g.deadline = time.AfterFunc(d, func() {
g.Close()
})
return nil
}
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
dialFunc := func(network, addr string, cfg *tls.Config) (net.Conn, error) {
pconn, err := dialFn(network, addr)
if err != nil {
return nil, err
}
cn := tls.Client(pconn, cfg)
if err := cn.Handshake(); err != nil {
pconn.Close()
return nil, err
}
state := cn.ConnectionState()
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
cn.Close()
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
}
return cn, nil
}
return &http2.Transport{
DialTLS: dialFunc,
TLSClientConfig: tlsConfig,
AllowHTTP: false,
DisableCompression: true,
PingTimeout: 0,
}
}
func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn, error) {
serviceName := "GunService"
if cfg.ServiceName != "" {
serviceName = cfg.ServiceName
}
reader, writer := io.Pipe()
request := &http.Request{
Method: http.MethodPost,
Body: reader,
URL: &url.URL{
Scheme: "https",
Host: cfg.Host,
Path: fmt.Sprintf("/%s/Tun", serviceName),
},
Proto: "HTTP/2",
ProtoMajor: 2,
ProtoMinor: 0,
Header: defaultHeader,
}
conn := &Conn{
request: request,
transport: transport,
writer: writer,
close: atomic.NewBool(false),
}
go conn.once.Do(conn.initRequest)
return conn, nil
}
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) {
dialFn := func(network, addr string) (net.Conn, error) {
return conn, nil
}
transport := NewHTTP2Client(dialFn, tlsConfig)
return StreamGunWithTransport(transport, cfg)
}

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
isTCP := network == TCP
switch network {
@ -173,7 +177,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
}
func newSearcher(major int) *searcher {
var s *searcher = nil
var s *searcher
switch major {
case 11:
s = &searcher{
@ -190,6 +194,8 @@ func newSearcher(major int) *searcher {
udpInpOffset: 8,
}
case 12:
fallthrough
case 13:
s = &searcher{
headSize: 64,
tcpItemSize: 744,

View File

@ -22,8 +22,8 @@ const (
)
var (
getExTcpTable uintptr
getExUdpTable uintptr
getExTCPTable uintptr
getExUDPTable uintptr
queryProcName uintptr
once sync.Once
@ -35,12 +35,12 @@ func initWin32API() error {
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
}
getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc)
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
}
getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc)
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
}
@ -76,10 +76,10 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
var fn uintptr
switch network {
case TCP:
fn = getExTcpTable
fn = getExTCPTable
class = tcpTablePidConn
case UDP:
fn = getExUdpTable
fn = getExUDPTable
class = udpTablePid
default:
return "", ErrInvalidNetwork

View File

@ -1,9 +1,12 @@
package resolver
import (
"context"
"errors"
"math/rand"
"net"
"strings"
"time"
"github.com/Dreamacro/clash/component/trie"
)
@ -18,6 +21,9 @@ var (
// DefaultHosts aim to resolve hosts
DefaultHosts = trie.New()
// DefaultDNSTimeout defined the default dns request timeout
DefaultDNSTimeout = time.Second * 5
)
var (
@ -52,18 +58,16 @@ func ResolveIPv4(host string) (net.IP, error) {
return DefaultResolver.ResolveIPv4(host)
}
ipAddrs, err := net.LookupIP(host)
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
for _, ip := range ipAddrs {
if ip4 := ip.To4(); ip4 != nil {
return ip4, nil
}
}
return nil, ErrIPNotFound
return ipAddrs[rand.Intn(len(ipAddrs))], nil
}
// ResolveIPv6 with a host, return ipv6
@ -90,31 +94,29 @@ func ResolveIPv6(host string) (net.IP, error) {
return DefaultResolver.ResolveIPv6(host)
}
ipAddrs, err := net.LookupIP(host)
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
for _, ip := range ipAddrs {
if ip.To4() == nil {
return ip, nil
}
}
return nil, ErrIPNotFound
return ipAddrs[rand.Intn(len(ipAddrs))], nil
}
// ResolveIP with a host, return ip
func ResolveIP(host string) (net.IP, error) {
// ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
return node.Data.(net.IP), nil
}
if DefaultResolver != nil {
if r != nil {
if DisableIPv6 {
return DefaultResolver.ResolveIPv4(host)
return r.ResolveIPv4(host)
}
return DefaultResolver.ResolveIP(host)
return r.ResolveIP(host)
} else if DisableIPv6 {
return ResolveIPv4(host)
}
@ -131,3 +133,8 @@ func ResolveIP(host string) (net.IP, error) {
return ipAddr.IP, nil
}
// ResolveIP with a host, return ip
func ResolveIP(host string) (net.IP, error) {
return ResolveIPWithResolver(host, DefaultResolver)
}

View File

@ -3,8 +3,10 @@ package snell
import (
"context"
"net"
"time"
"github.com/Dreamacro/clash/component/pool"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
)
@ -60,6 +62,9 @@ func (pc *PoolConn) Write(b []byte) (int, error) {
}
func (pc *PoolConn) Close() error {
// clash use SetReadDeadline to break bidirectional copy between client and server.
// reset it before reuse connection to avoid io timeout error.
pc.Snell.Conn.SetReadDeadline(time.Time{})
pc.pool.Put(pc.Snell)
return nil
}

View File

@ -14,6 +14,7 @@ import (
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/ssr/tools"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/go-shadowsocks2/core"
)

View File

@ -12,6 +12,7 @@ import (
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/go-shadowsocks2/core"
)

View File

@ -122,11 +122,7 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
}
}
if c := node.getChild(dotWildcard); c != nil {
return c
}
return nil
return node.getChild(dotWildcard)
}
// New returns a new, empty Trie.

View File

@ -5,6 +5,7 @@ import (
"encoding/binary"
"errors"
"io"
"sync"
"github.com/Dreamacro/clash/common/pool"
)
@ -15,6 +16,8 @@ type aeadWriter struct {
nonce [32]byte
count uint16
iv []byte
writeLock sync.Mutex
}
func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter {
@ -22,8 +25,12 @@ func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter {
}
func (w *aeadWriter) Write(b []byte) (n int, err error) {
w.writeLock.Lock()
buf := pool.Get(pool.RelayBufferSize)
defer pool.Put(buf)
defer func() {
w.writeLock.Unlock()
pool.Put(buf)
}()
length := len(b)
for {
if length == 0 {

View File

@ -6,6 +6,7 @@ import (
"crypto/cipher"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/binary"
"errors"
"hash/fnv"
@ -34,6 +35,7 @@ type Conn struct {
respBodyKey []byte
respV byte
security byte
isAead bool
received bool
}
@ -57,11 +59,12 @@ func (vc *Conn) Read(b []byte) (int, error) {
func (vc *Conn) sendRequest() error {
timestamp := time.Now()
h := hmac.New(md5.New, vc.id.UUID.Bytes())
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
_, err := vc.Conn.Write(h.Sum(nil))
if err != nil {
return err
if !vc.isAead {
h := hmac.New(md5.New, vc.id.UUID.Bytes())
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
if _, err := vc.Conn.Write(h.Sum(nil)); err != nil {
return err
}
}
buf := &bytes.Buffer{}
@ -99,30 +102,77 @@ func (vc *Conn) sendRequest() error {
fnv1a.Write(buf.Bytes())
buf.Write(fnv1a.Sum(nil))
block, err := aes.NewCipher(vc.id.CmdKey)
if err != nil {
if !vc.isAead {
block, err := aes.NewCipher(vc.id.CmdKey)
if err != nil {
return err
}
stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
_, err = vc.Conn.Write(buf.Bytes())
return err
}
stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp))
stream.XORKeyStream(buf.Bytes(), buf.Bytes())
_, err = vc.Conn.Write(buf.Bytes())
var fixedLengthCmdKey [16]byte
copy(fixedLengthCmdKey[:], vc.id.CmdKey)
vmessout := sealVMessAEADHeader(fixedLengthCmdKey, buf.Bytes(), timestamp)
_, err := vc.Conn.Write(vmessout)
return err
}
func (vc *Conn) recvResponse() error {
block, err := aes.NewCipher(vc.respBodyKey[:])
if err != nil {
return err
}
var buf []byte
if !vc.isAead {
block, err := aes.NewCipher(vc.respBodyKey[:])
if err != nil {
return err
}
stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:])
buf := make([]byte, 4)
_, err = io.ReadFull(vc.Conn, buf)
if err != nil {
return err
stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:])
buf = make([]byte, 4)
_, err = io.ReadFull(vc.Conn, buf)
if err != nil {
return err
}
stream.XORKeyStream(buf, buf)
} else {
aeadResponseHeaderLengthEncryptionKey := kdf(vc.respBodyKey[:], kdfSaltConstAEADRespHeaderLenKey)[:16]
aeadResponseHeaderLengthEncryptionIV := kdf(vc.respBodyIV[:], kdfSaltConstAEADRespHeaderLenIV)[:12]
aeadResponseHeaderLengthEncryptionKeyAESBlock, _ := aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)
aeadResponseHeaderLengthEncryptionAEAD, _ := cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)
aeadEncryptedResponseHeaderLength := make([]byte, 18)
if _, err := io.ReadFull(vc.Conn, aeadEncryptedResponseHeaderLength); err != nil {
return err
}
decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil)
if err != nil {
return err
}
decryptedResponseHeaderLength := binary.BigEndian.Uint16(decryptedResponseHeaderLengthBinaryBuffer)
aeadResponseHeaderPayloadEncryptionKey := kdf(vc.respBodyKey[:], kdfSaltConstAEADRespHeaderPayloadKey)[:16]
aeadResponseHeaderPayloadEncryptionIV := kdf(vc.respBodyIV[:], kdfSaltConstAEADRespHeaderPayloadIV)[:12]
aeadResponseHeaderPayloadEncryptionKeyAESBlock, _ := aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)
aeadResponseHeaderPayloadEncryptionAEAD, _ := cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)
encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)
if _, err := io.ReadFull(vc.Conn, encryptedResponseHeaderBuffer); err != nil {
return err
}
buf, err = aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil)
if err != nil {
return err
}
if len(buf) < 4 {
return errors.New("unexpected buffer length")
}
}
stream.XORKeyStream(buf, buf)
if buf[0] != vc.respV {
return errors.New("unexpected response header")
@ -147,7 +197,7 @@ func hashTimestamp(t time.Time) []byte {
}
// newConn return a Conn instance
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, error) {
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool) (*Conn, error) {
randBytes := make([]byte, 33)
rand.Read(randBytes)
reqBodyIV := make([]byte, 16)
@ -156,8 +206,22 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, err
copy(reqBodyKey[:], randBytes[16:32])
respV := randBytes[32]
respBodyKey := md5.Sum(reqBodyKey[:])
respBodyIV := md5.Sum(reqBodyIV[:])
var (
respBodyKey []byte
respBodyIV []byte
)
if isAead {
bodyKey := sha256.Sum256(reqBodyKey)
bodyIV := sha256.Sum256(reqBodyIV)
respBodyKey = bodyKey[:16]
respBodyIV = bodyIV[:16]
} else {
bodyKey := md5.Sum(reqBodyKey)
bodyIV := md5.Sum(reqBodyIV)
respBodyKey = bodyKey[:]
respBodyIV = bodyIV[:]
}
var writer io.Writer
var reader io.Reader
@ -202,6 +266,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, err
reader: reader,
writer: writer,
security: security,
isAead: isAead,
}
if err := c.sendRequest(); err != nil {
return nil, err

View File

@ -90,10 +90,7 @@ func (hc *h2Conn) Close() error {
if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil {
return err
}
if err := hc.Conn.Close(); err != nil {
return err
}
return nil
return hc.Conn.Close()
}
func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) {

103
component/vmess/header.go Normal file
View File

@ -0,0 +1,103 @@
package vmess
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"hash"
"hash/crc32"
"time"
)
const (
kdfSaltConstAuthIDEncryptionKey = "AES Auth ID Encryption"
kdfSaltConstAEADRespHeaderLenKey = "AEAD Resp Header Len Key"
kdfSaltConstAEADRespHeaderLenIV = "AEAD Resp Header Len IV"
kdfSaltConstAEADRespHeaderPayloadKey = "AEAD Resp Header Key"
kdfSaltConstAEADRespHeaderPayloadIV = "AEAD Resp Header IV"
kdfSaltConstVMessAEADKDF = "VMess AEAD KDF"
kdfSaltConstVMessHeaderPayloadAEADKey = "VMess Header AEAD Key"
kdfSaltConstVMessHeaderPayloadAEADIV = "VMess Header AEAD Nonce"
kdfSaltConstVMessHeaderPayloadLengthAEADKey = "VMess Header AEAD Key_Length"
kdfSaltConstVMessHeaderPayloadLengthAEADIV = "VMess Header AEAD Nonce_Length"
)
func kdf(key []byte, path ...string) []byte {
hmacCreator := &hMacCreator{value: []byte(kdfSaltConstVMessAEADKDF)}
for _, v := range path {
hmacCreator = &hMacCreator{value: []byte(v), parent: hmacCreator}
}
hmacf := hmacCreator.Create()
hmacf.Write(key)
return hmacf.Sum(nil)
}
type hMacCreator struct {
parent *hMacCreator
value []byte
}
func (h *hMacCreator) Create() hash.Hash {
if h.parent == nil {
return hmac.New(sha256.New, h.value)
}
return hmac.New(h.parent.Create, h.value)
}
func createAuthID(cmdKey []byte, time int64) [16]byte {
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, time)
random := make([]byte, 4)
rand.Read(random)
buf.Write(random)
zero := crc32.ChecksumIEEE(buf.Bytes())
binary.Write(buf, binary.BigEndian, zero)
aesBlock, _ := aes.NewCipher(kdf(cmdKey[:], kdfSaltConstAuthIDEncryptionKey)[:16])
var result [16]byte
aesBlock.Encrypt(result[:], buf.Bytes())
return result
}
func sealVMessAEADHeader(key [16]byte, data []byte, t time.Time) []byte {
generatedAuthID := createAuthID(key[:], t.Unix())
connectionNonce := make([]byte, 8)
rand.Read(connectionNonce)
aeadPayloadLengthSerializedByte := make([]byte, 2)
binary.BigEndian.PutUint16(aeadPayloadLengthSerializedByte, uint16(len(data)))
var payloadHeaderLengthAEADEncrypted []byte
{
payloadHeaderLengthAEADKey := kdf(key[:], kdfSaltConstVMessHeaderPayloadLengthAEADKey, string(generatedAuthID[:]), string(connectionNonce))[:16]
payloadHeaderLengthAEADNonce := kdf(key[:], kdfSaltConstVMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
payloadHeaderLengthAEADAESBlock, _ := aes.NewCipher(payloadHeaderLengthAEADKey)
payloadHeaderAEAD, _ := cipher.NewGCM(payloadHeaderLengthAEADAESBlock)
payloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:])
}
var payloadHeaderAEADEncrypted []byte
{
payloadHeaderAEADKey := kdf(key[:], kdfSaltConstVMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce))[:16]
payloadHeaderAEADNonce := kdf(key[:], kdfSaltConstVMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
payloadHeaderAEADAESBlock, _ := aes.NewCipher(payloadHeaderAEADKey)
payloadHeaderAEAD, _ := cipher.NewGCM(payloadHeaderAEADAESBlock)
payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:])
}
var outputBuffer = &bytes.Buffer{}
outputBuffer.Write(generatedAuthID[:])
outputBuffer.Write(payloadHeaderLengthAEADEncrypted)
outputBuffer.Write(connectionNonce)
outputBuffer.Write(payloadHeaderAEADEncrypted)
return outputBuffer.Bytes()
}

View File

@ -1,6 +1,7 @@
package vmess
import (
"bufio"
"bytes"
"fmt"
"math/rand"
@ -12,7 +13,7 @@ import (
type httpConn struct {
net.Conn
cfg *HTTPConfig
rhandshake bool
reader *bufio.Reader
whandshake bool
}
@ -25,8 +26,8 @@ type HTTPConfig struct {
// Read implements net.Conn.Read()
func (hc *httpConn) Read(b []byte) (int, error) {
if hc.rhandshake {
n, err := hc.Conn.Read(b)
if hc.reader != nil {
n, err := hc.reader.Read(b)
return n, err
}
@ -40,8 +41,8 @@ func (hc *httpConn) Read(b []byte) (int, error) {
return 0, err
}
hc.rhandshake = true
return hc.Conn.Read(b)
hc.reader = reader.R
return reader.R.Read(b)
}
// Write implements io.Writer.

View File

@ -61,6 +61,7 @@ type Client struct {
user []*ID
uuid *uuid.UUID
security Security
isAead bool
}
// Config of vmess
@ -70,12 +71,13 @@ type Config struct {
Security string
Port string
HostName string
IsAead bool
}
// StreamConn return a Conn with net.Conn and DstAddr
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
r := rand.Intn(len(c.user))
return newConn(conn, c.user[r], dst, c.security)
return newConn(conn, c.user[r], dst, c.security, c.isAead)
}
// NewClient return Client instance
@ -106,5 +108,6 @@ func NewClient(config Config) (*Client, error) {
user: newAlterIDs(newID(&uid), config.AlterID),
uuid: &uid,
security: security,
isAead: config.IsAead,
}, nil
}

View File

@ -66,7 +66,7 @@ func Init(dir string) error {
if err != nil {
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
}
f.Write([]byte(`port: 7890`))
f.Write([]byte(`mixed-port: 7890`))
f.Close()
}

View File

@ -69,8 +69,21 @@ type PacketConn interface {
type ProxyAdapter interface {
Name() string
Type() AdapterType
// StreamConn wraps a protocol around net.Conn with Metadata.
//
// Examples:
// conn, _ := net.Dial("tcp", "host:port")
// conn, _ = adapter.StreamConn(conn, metadata)
//
// It returns a C.Conn with protocol which start with
// a new session (if any)
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
// DialContext return a C.Conn with protocol which
// contains multiplexing-related reuse logic (if any)
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
DialUDP(metadata *Metadata) (PacketConn, error)
SupportUDP() bool
MarshalJSON() ([]byte, error)

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns"
)
@ -28,8 +29,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
// a default ip dns
ip = net.ParseIP(c.host)
} else {
var err error
if ip, err = c.r.ResolveIP(c.host); err != nil {
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
}
}

View File

@ -9,6 +9,7 @@ import (
"net/http"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns"
)
@ -83,12 +84,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return nil, err
}
ip, err := r.ResolveIPv4(host)
ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
},
},
}

View File

@ -145,7 +145,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
}
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
fast, ctx := picker.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
for _, client := range clients {
r := client
fast.Go(func() (interface{}, error) {

14
go.mod
View File

@ -3,20 +3,20 @@ module github.com/Dreamacro/clash
go 1.16
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.6
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.1.1
github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/go-chi/chi/v5 v5.0.3
github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.0.0+incompatible
github.com/gorilla/websocket v1.4.2
github.com/miekg/dns v1.1.40
github.com/miekg/dns v1.1.42
github.com/oschwald/geoip2-golang v1.5.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.7.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
golang.org/x/net v0.0.0-20210508051633-16afe75a6701
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096
gopkg.in/yaml.v2 v2.4.0
)

48
go.sum
View File

@ -1,20 +1,20 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
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/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
github.com/miekg/dns v1.1.42/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/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
@ -31,36 +31,26 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
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.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA=
golang.org/x/net v0.0.0-20210508051633-16afe75a6701/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo=
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -4,7 +4,7 @@ import (
"net/http"
"net/url"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
)
// When name is composed of a partial escape string, Golang does not unescape it

View File

@ -11,7 +11,7 @@ import (
P "github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)

View File

@ -8,10 +8,10 @@ import (
"time"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/gorilla/websocket"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/gorilla/websocket"
)
func connectionRouter() http.Handler {

View File

@ -7,7 +7,7 @@ import (
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)

View File

@ -13,7 +13,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)

View File

@ -5,7 +5,7 @@ import (
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)

View File

@ -11,7 +11,7 @@ import (
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
"github.com/go-chi/render"
"github.com/gorilla/websocket"

View File

@ -45,7 +45,7 @@ func init() {
func main() {
if version {
fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime)
fmt.Printf("Clash %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
return
}

View File

@ -16,19 +16,19 @@ import (
"github.com/Dreamacro/clash/tunnel"
)
type HttpListener struct {
type HTTPListener struct {
net.Listener
address string
closed bool
cache *cache.Cache
}
func NewHttpProxy(addr string) (*HttpListener, error) {
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)}
hl := &HTTPListener{l, addr, false, cache.New(30 * time.Second)}
go func() {
log.Infoln("HTTP proxy listening at: %s", addr)
@ -48,12 +48,12 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
return hl, nil
}
func (l *HttpListener) Close() {
func (l *HTTPListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *HttpListener) Address() string {
func (l *HTTPListener) Address() string {
return l.address
}

View File

@ -19,7 +19,7 @@ var (
socksListener *socks.SockListener
socksUDPListener *socks.SockUDPListener
httpListener *http.HttpListener
httpListener *http.HTTPListener
redirListener *redir.RedirListener
redirUDPListener *redir.RedirUDPListener
tproxyListener *redir.TProxyListener
@ -78,7 +78,7 @@ func ReCreateHTTP(port int) error {
}
var err error
httpListener, err = http.NewHttpProxy(addr)
httpListener, err = http.NewHTTPProxy(addr)
if err != nil {
return err
}
@ -316,9 +316,8 @@ func genAddr(host string, port int, allowLan bool) string {
if allowLan {
if host == "*" {
return fmt.Sprintf(":%d", port)
} else {
return fmt.Sprintf("%s:%d", host, port)
}
return fmt.Sprintf("%s:%d", host, port)
}
return fmt.Sprintf("127.0.0.1:%d", port)

View File

@ -1,3 +1,5 @@
// +build !darwin,!linux,!freebsd
package redir
import (
@ -8,5 +10,5 @@ import (
)
func parserPacket(conn net.Conn) (socks5.Addr, error) {
return nil, errors.New("Windows not support yet")
return nil, errors.New("system not support yet")
}

View File

@ -48,7 +48,7 @@ func NewTProxy(addr string) (*TProxyListener, error) {
}
continue
}
go rl.handleRedir(c)
go rl.handleTProxy(c)
}
}()
@ -64,7 +64,7 @@ func (l *TProxyListener) Address() string {
return l.address
}
func (l *TProxyListener) handleRedir(conn net.Conn) {
func (l *TProxyListener) handleTProxy(conn net.Conn) {
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
conn.(*net.TCPConn).SetKeepAlive(true)
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))

View File

@ -75,5 +75,5 @@ func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *ne
lAddr: lAddr,
buf: buf,
}
tunnel.AddPacket(adapters.NewPacket(target, pkt, C.REDIR))
tunnel.AddPacket(adapters.NewPacket(target, pkt, C.TPROXY))
}

View File

@ -45,15 +45,15 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
return strings.EqualFold(cached.(string), ps.process)
}
func (p *Process) Adapter() string {
return p.adapter
func (ps *Process) Adapter() string {
return ps.adapter
}
func (p *Process) Payload() string {
return p.process
func (ps *Process) Payload() string {
return ps.process
}
func (p *Process) ShouldResolveIP() bool {
func (ps *Process) ShouldResolveIP() bool {
return false
}

View File

@ -21,6 +21,9 @@ func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
req := ctx.Request()
conn := ctx.Conn()
// make outbound close after inbound error or close
conn = &connLinker{conn, outbound}
inboundReader := bufio.NewReader(conn)
outboundReader := bufio.NewReader(outbound)
@ -29,7 +32,6 @@ func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
for {
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
req.Header.Set("Connection", "close")
req.RequestURI = ""
inbound.RemoveHopByHopHeaders(req.Header)
err := req.Write(outbound)
@ -54,7 +56,12 @@ func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
goto handleResponse
}
if keepAlive || resp.ContentLength >= 0 {
// close conn when header `Connection` is `close`
if resp.Header.Get("Connection") == "close" {
keepAlive = false
}
if keepAlive {
resp.Header.Set("Proxy-Connection", "keep-alive")
resp.Header.Set("Connection", "keep-alive")
resp.Header.Set("Keep-Alive", "timeout=4")
@ -67,14 +74,6 @@ func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
break
}
// even if resp.Write write body to the connection, but some http request have to Copy to close it
buf := pool.Get(pool.RelayBufferSize)
_, err = io.CopyBuffer(conn, resp.Body, buf)
pool.Put(buf)
if err != nil && err != io.EOF {
break
}
req, err = http.ReadRequest(inboundReader)
if err != nil {
break
@ -163,3 +162,31 @@ func relay(leftConn, rightConn net.Conn) {
rightConn.SetReadDeadline(time.Now())
<-ch
}
// connLinker make the two net.Conn correlated, for temporary resolution of leaks.
// There is no better way to do this for now.
type connLinker struct {
net.Conn
linker net.Conn
}
func (conn *connLinker) Read(b []byte) (n int, err error) {
n, err = conn.Conn.Read(b)
if err != nil {
conn.linker.Close()
}
return n, err
}
func (conn *connLinker) Write(b []byte) (n int, err error) {
n, err = conn.Conn.Write(b)
if err != nil {
conn.linker.Close()
}
return n, err
}
func (conn *connLinker) Close() error {
conn.linker.Close()
return conn.Conn.Close()
}

View File

@ -122,6 +122,11 @@ func preHandleMetadata(metadata *C.Metadata) error {
if ip := net.ParseIP(metadata.Host); ip != nil {
metadata.DstIP = ip
metadata.Host = ""
if ip.To4() != nil {
metadata.AddrType = C.AtypIPv4
} else {
metadata.AddrType = C.AtypIPv6
}
}
// preprocess enhanced-mode metadata
@ -330,7 +335,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
}
if metadata.NetWork == C.UDP && !adapter.SupportUDP() {
log.Debugln("%v UDP is not supported", adapter.Name())
log.Debugln("%s UDP is not supported", adapter.Name())
continue
}
return adapter, rule, nil