Compare commits

..

27 Commits

Author SHA1 Message Date
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
6c83ff3496 Chore: update dependencies 2021-03-10 21:13:23 +08:00
f7f97ef625 Fix: some HTTP proxy request broken 2021-03-10 16:23:55 +08:00
5acdd72a1d Fix: remove host if host is ip string 2021-03-10 12:49:30 +08:00
f53686103d Chore: reset udp timeout after sending each packet (#1260) 2021-02-26 10:40:55 +08:00
f63c9eb22f Chore: update staticcheck command on actions 2021-02-21 19:37:37 +08:00
a37243cf30 Fix: store cache correctly 2021-02-21 01:07:22 +08:00
50 changed files with 904 additions and 246 deletions

View File

@ -26,7 +26,7 @@ jobs:
run: | run: |
go test ./... go test ./...
go vet ./... go vet ./...
go get -u honnef.co/go/tools/cmd/staticcheck go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck -- $(go list ./...) staticcheck -- $(go list ./...)
- name: Build - name: Build
@ -44,4 +44,3 @@ jobs:
with: with:
files: bin/* files: bin/*
draft: true draft: true
prerelease: true

View File

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

View File

@ -43,3 +43,19 @@ func RemoveHopByHopHeaders(header http.Header) {
header.Del(strings.TrimSpace(h)) header.Del(strings.TrimSpace(h))
} }
} }
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
func RemoveExtraHTTPHostPort(req *http.Request) {
host := req.Host
if host == "" {
host = req.URL.Host
}
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
host = pHost
}
req.Host = host
req.URL.Host = host
}

View File

@ -51,13 +51,15 @@ func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, nil return c, nil
} }
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, 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 {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = h.StreamConn(c, metadata) c, err = h.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -73,13 +73,15 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
return c, err return c, err
} }
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, 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 {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata) c, err = ss.StreamConn(c, metadata)
return NewConn(c, ss), err return NewConn(c, ss), err
} }
@ -92,6 +94,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
addr, err := resolveUDPAddr("udp", ss.addr) addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil { if err != nil {
pc.Close()
return nil, err return nil, err
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/Dreamacro/clash/component/ssr/obfs" "github.com/Dreamacro/clash/component/ssr/obfs"
"github.com/Dreamacro/clash/component/ssr/protocol" "github.com/Dreamacro/clash/component/ssr/protocol"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowstream" "github.com/Dreamacro/go-shadowsocks2/shadowstream"
@ -57,13 +58,15 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
return c, err return c, err
} }
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, 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 {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ssr.StreamConn(c, metadata) c, err = ssr.StreamConn(c, metadata)
return NewConn(c, ssr), err return NewConn(c, ssr), err
} }
@ -76,6 +79,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
addr, err := resolveUDPAddr("udp", ssr.addr) addr, err := resolveUDPAddr("udp", ssr.addr)
if err != nil { if err != nil {
pc.Close()
return nil, err return nil, err
} }

View File

@ -55,7 +55,7 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, err return c, err
} }
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, 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()
if err != nil { if err != nil {
@ -63,7 +63,10 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
} }
port, _ := strconv.Atoi(metadata.DstPort) 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 return NewConn(c, s), err
} }
@ -73,6 +76,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = s.StreamConn(c, metadata) c, err = s.StreamConn(c, metadata)
return NewConn(c, s), err return NewConn(c, s), err
} }

View File

@ -58,13 +58,15 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, nil return c, nil
} }
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, 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 {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = ss.StreamConn(c, metadata) c, err = ss.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
@ -88,11 +90,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
c = cc c = cc
} }
defer func() { defer safeConnClose(c, err)
if err != nil {
c.Close()
}
}()
tcpKeepAlive(c) tcpKeepAlive(c)
var user *socks5.User var user *socks5.User

View File

@ -2,19 +2,28 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"encoding/json" "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" "github.com/Dreamacro/clash/component/trojan"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/http2"
) )
type Trojan struct { type Trojan struct {
*Base *Base
instance *trojan.Trojan instance *trojan.Trojan
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *http2.Transport
} }
type TrojanOption struct { type TrojanOption struct {
@ -26,10 +35,18 @@ type TrojanOption struct {
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
} }
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) {
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 { 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)
} }
@ -38,12 +55,30 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, err return c, err
} }
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 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) 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)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = t.StreamConn(c, metadata) c, err = t.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
@ -52,18 +87,31 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return NewConn(c, t), err return NewConn(c, t), err
} }
func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 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)
}
} else {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
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)
} }
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 {
@ -95,7 +143,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tOption.ServerName = option.SNI tOption.ServerName = option.SNI
} }
return &Trojan{ t := &Trojan{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
@ -103,5 +151,33 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
udp: option.UDP, udp: option.UDP,
}, },
instance: trojan.New(tOption), 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)) 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 ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -10,15 +11,23 @@ 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" "github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/http2"
) )
type Vmess struct { type Vmess struct {
*Base *Base
client *vmess.Client client *vmess.Client
option *VmessOption option *VmessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *http2.Transport
} }
type VmessOption struct { type VmessOption struct {
@ -33,6 +42,7 @@ type VmessOption struct {
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
@ -50,6 +60,10 @@ type HTTP2Options struct {
Path string `proxy:"path,omitempty"` Path string `proxy:"path,omitempty"`
} }
type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
}
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 {
@ -129,6 +143,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} }
c, err = vmess.StreamH2Conn(c, h2Opts) c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
default: default:
// handle TLS // handle TLS
if v.option.TLS { if v.option.TLS {
@ -154,19 +170,36 @@ 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))
} }
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 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) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata) c, err = v.StreamConn(c, metadata)
return NewConn(c, v), err return NewConn(c, v), err
} }
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func (v *Vmess) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp, so clash 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() {
ip, err := resolver.ResolveIP(metadata.Host) ip, err := resolver.ResolveIP(metadata.Host)
if err != nil { if err != nil {
@ -175,17 +208,33 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
metadata.DstIP = ip metadata.DstIP = ip
} }
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) ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
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 {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
tcpKeepAlive(c) tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata) c, err = v.StreamConn(c, metadata)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
} }
@ -197,15 +246,20 @@ func NewVmess(option VmessOption) (*Vmess, error) {
Security: security, Security: security,
HostName: option.Server, HostName: option.Server,
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
IsAead: option.AlterID == 0,
}) })
if err != nil { if err != nil {
return nil, err 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{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
@ -214,7 +268,39 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}, },
client: client, client: client,
option: &option, option: &option,
}, nil }
if option.Network == "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 { func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {

View File

@ -79,7 +79,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
} }
// tolerance // tolerance
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { if u.fastNode == nil || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast u.fastNode = fast
} }

View File

@ -16,16 +16,24 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
} }
ip := net.ParseIP(host) ip := net.ParseIP(host)
if ip != nil { if ip == nil {
if ip.To4() != nil {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypIPv4, AddrType: C.AtypDomainName,
Host: "", Host: host,
DstIP: ip, DstIP: nil,
DstPort: port, DstPort: port,
} }
return return
} else { } else if ip4 := ip.To4(); ip4 != nil {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip4,
DstPort: port,
}
return
}
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypIPv6, AddrType: C.AtypIPv6,
Host: "", Host: "",
@ -34,16 +42,6 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
} }
return return
} }
} else {
addr = &C.Metadata{
AddrType: C.AtypDomainName,
Host: host,
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 {

View File

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

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

@ -0,0 +1,250 @@
// Modified from: https://github.com/Qv2ray/gun-lite
// License: MIT
package gun
import (
"bufio"
"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"},
}
)
type DialFn = func(network, addr string) (net.Conn, error)
type Conn struct {
response *http.Response
request *http.Request
client *http.Client
writer *io.PipeWriter
once sync.Once
close *atomic.Bool
err error
remain int
br *bufio.Reader
}
type Config struct {
ServiceName string
Host string
}
func (g *Conn) initRequest() {
response, err := g.client.Do(g.request)
if err != nil {
g.err = err
g.writer.Close()
return
}
if !g.close.Load() {
g.response = response
} 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.br != nil {
remain := g.br.Buffered()
if len(b) < remain {
remain = len(b)
}
n, err = g.br.Read(b[:remain])
if g.br.Buffered() == 0 {
g.br = nil
}
return
} else if g.remain != 0 {
size := g.remain
if len(b) < size {
size = len(b)
}
n, err = g.response.Body.Read(b[:size])
g.remain -= n
return
} else if g.response == nil {
return 0, net.ErrClosed
}
// 0x00 grpclength(uint32) 0x0A uleb128 payload
buf := make([]byte, 6)
_, err = io.ReadFull(g.response.Body, buf)
if err != nil {
return 0, err
}
br := bufio.NewReaderSize(g.response.Body, 16)
protobufPayloadLen, err := binary.ReadUvarint(br)
if err != nil {
return 0, ErrInvalidLength
}
bufferedSize := br.Buffered()
if len(b) < bufferedSize {
n, err = br.Read(b)
g.br = br
g.remain = int(protobufPayloadLen) - n - g.br.Buffered()
return
}
_, err = br.Read(b[:bufferedSize])
if err != nil {
return
}
offset := int(protobufPayloadLen)
if len(b) < int(protobufPayloadLen) {
offset = len(b)
}
if offset == bufferedSize {
return bufferedSize, nil
}
n, err = io.ReadFull(g.response.Body, b[bufferedSize:offset])
if err != nil {
return
}
remain := int(protobufPayloadLen) - offset
if remain > 0 {
g.remain = remain
}
return offset, nil
}
func (g *Conn) Write(b []byte) (n int, err error) {
protobufHeader := appendUleb128([]byte{0x0A}, uint64(len(b)))
grpcHeader := make([]byte, 5)
grpcPayloadLen := uint32(len(protobufHeader) + len(b))
binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen)
buffers := net.Buffers{grpcHeader, protobufHeader, b}
_, err = buffers.WriteTo(g.writer)
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) SetDeadline(t time.Time) error { return nil }
func (g *Conn) SetReadDeadline(t time.Time) error { return nil }
func (g *Conn) SetWriteDeadline(t time.Time) error { 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,
ReadIdleTimeout: 0,
PingTimeout: 0,
}
}
func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn, error) {
serviceName := "GunService"
if cfg.ServiceName != "" {
serviceName = cfg.ServiceName
}
client := &http.Client{
Transport: transport,
}
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,
client: client,
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)
}

38
component/gun/leb128.go Normal file
View File

@ -0,0 +1,38 @@
// 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

@ -173,7 +173,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
} }
func newSearcher(major int) *searcher { func newSearcher(major int) *searcher {
var s *searcher = nil var s *searcher
switch major { switch major {
case 11: case 11:
s = &searcher{ s = &searcher{

View File

@ -22,8 +22,8 @@ const (
) )
var ( var (
getExTcpTable uintptr getExTCPTable uintptr
getExUdpTable uintptr getExUDPTable uintptr
queryProcName uintptr queryProcName uintptr
once sync.Once once sync.Once
@ -35,12 +35,12 @@ func initWin32API() error {
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.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 { if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error()) 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 { if err != nil {
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error()) 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 var fn uintptr
switch network { switch network {
case TCP: case TCP:
fn = getExTcpTable fn = getExTCPTable
class = tcpTablePidConn class = tcpTablePidConn
case UDP: case UDP:
fn = getExUdpTable fn = getExUDPTable
class = udpTablePid class = udpTablePid
default: default:
return "", ErrInvalidNetwork return "", ErrInvalidNetwork

View File

@ -26,7 +26,6 @@ type cache struct {
type CacheFile struct { type CacheFile struct {
path string path string
model *cache model *cache
enc *gob.Encoder
buf *bytes.Buffer buf *bytes.Buffer
mux sync.Mutex mux sync.Mutex
} }
@ -39,15 +38,11 @@ func (c *CacheFile) SetSelected(group, selected string) {
c.mux.Lock() c.mux.Lock()
defer c.mux.Unlock() defer c.mux.Unlock()
model, err := c.element() model := c.element()
if err != nil {
log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error())
return
}
model.Selected[group] = selected model.Selected[group] = selected
c.buf.Reset() c.buf.Reset()
if err := c.enc.Encode(model); err != nil { if err := gob.NewEncoder(c.buf).Encode(model); err != nil {
log.Warnln("[CacheFile] encode gob failed: %s", err.Error()) log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
return return
} }
@ -66,11 +61,7 @@ func (c *CacheFile) SelectedMap() map[string]string {
c.mux.Lock() c.mux.Lock()
defer c.mux.Unlock() defer c.mux.Unlock()
model, err := c.element() model := c.element()
if err != nil {
log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error())
return nil
}
mapping := map[string]string{} mapping := map[string]string{}
for k, v := range model.Selected { for k, v := range model.Selected {
@ -79,9 +70,9 @@ func (c *CacheFile) SelectedMap() map[string]string {
return mapping return mapping
} }
func (c *CacheFile) element() (*cache, error) { func (c *CacheFile) element() *cache {
if c.model != nil { if c.model != nil {
return c.model, nil return c.model
} }
model := &cache{ model := &cache{
@ -90,24 +81,19 @@ func (c *CacheFile) element() (*cache, error) {
if buf, err := ioutil.ReadFile(c.path); err == nil { if buf, err := ioutil.ReadFile(c.path); err == nil {
bufReader := bytes.NewBuffer(buf) bufReader := bytes.NewBuffer(buf)
dec := gob.NewDecoder(bufReader) gob.NewDecoder(bufReader).Decode(model)
if err := dec.Decode(model); err != nil {
return nil, err
}
} }
c.model = model c.model = model
return c.model, nil return c.model
} }
// Cache return singleton of CacheFile // Cache return singleton of CacheFile
func Cache() *CacheFile { func Cache() *CacheFile {
initOnce.Do(func() { initOnce.Do(func() {
buf := &bytes.Buffer{}
defaultCache = &CacheFile{ defaultCache = &CacheFile{
path: C.Path.Cache(), path: C.Path.Cache(),
buf: buf, buf: &bytes.Buffer{},
enc: gob.NewEncoder(buf),
} }
}) })

View File

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

View File

@ -5,6 +5,7 @@ import (
"net" "net"
"github.com/Dreamacro/clash/component/pool" "github.com/Dreamacro/clash/component/pool"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowaead"
) )

View File

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

View File

@ -12,6 +12,7 @@ import (
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/go-shadowsocks2/core" "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 node.getChild(dotWildcard)
return c
}
return nil
} }
// New returns a new, empty Trie. // New returns a new, empty Trie.

View File

@ -6,6 +6,7 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/hmac" "crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
"hash/fnv" "hash/fnv"
@ -34,6 +35,7 @@ type Conn struct {
respBodyKey []byte respBodyKey []byte
respV byte respV byte
security byte security byte
isAead bool
received bool received bool
} }
@ -57,12 +59,13 @@ func (vc *Conn) Read(b []byte) (int, error) {
func (vc *Conn) sendRequest() error { func (vc *Conn) sendRequest() error {
timestamp := time.Now() timestamp := time.Now()
if !vc.isAead {
h := hmac.New(md5.New, vc.id.UUID.Bytes()) h := hmac.New(md5.New, vc.id.UUID.Bytes())
binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) binary.Write(h, binary.BigEndian, uint64(timestamp.Unix()))
_, err := vc.Conn.Write(h.Sum(nil)) if _, err := vc.Conn.Write(h.Sum(nil)); err != nil {
if err != nil {
return err return err
} }
}
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -99,6 +102,7 @@ func (vc *Conn) sendRequest() error {
fnv1a.Write(buf.Bytes()) fnv1a.Write(buf.Bytes())
buf.Write(fnv1a.Sum(nil)) buf.Write(fnv1a.Sum(nil))
if !vc.isAead {
block, err := aes.NewCipher(vc.id.CmdKey) block, err := aes.NewCipher(vc.id.CmdKey)
if err != nil { if err != nil {
return err return err
@ -110,19 +114,65 @@ func (vc *Conn) sendRequest() error {
return err return err
} }
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 { func (vc *Conn) recvResponse() error {
var buf []byte
if !vc.isAead {
block, err := aes.NewCipher(vc.respBodyKey[:]) block, err := aes.NewCipher(vc.respBodyKey[:])
if err != nil { if err != nil {
return err return err
} }
stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:]) stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:])
buf := make([]byte, 4) buf = make([]byte, 4)
_, err = io.ReadFull(vc.Conn, buf) _, err = io.ReadFull(vc.Conn, buf)
if err != nil { if err != nil {
return err return err
} }
stream.XORKeyStream(buf, buf) 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")
}
}
if buf[0] != vc.respV { if buf[0] != vc.respV {
return errors.New("unexpected response header") return errors.New("unexpected response header")
@ -147,7 +197,7 @@ func hashTimestamp(t time.Time) []byte {
} }
// newConn return a Conn instance // 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) randBytes := make([]byte, 33)
rand.Read(randBytes) rand.Read(randBytes)
reqBodyIV := make([]byte, 16) 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]) copy(reqBodyKey[:], randBytes[16:32])
respV := randBytes[32] respV := randBytes[32]
respBodyKey := md5.Sum(reqBodyKey[:]) var (
respBodyIV := md5.Sum(reqBodyIV[:]) 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 writer io.Writer
var reader io.Reader var reader io.Reader
@ -202,6 +266,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, err
reader: reader, reader: reader,
writer: writer, writer: writer,
security: security, security: security,
isAead: isAead,
} }
if err := c.sendRequest(); err != nil { if err := c.sendRequest(); err != nil {
return nil, err 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 { if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil {
return err return err
} }
if err := hc.Conn.Close(); err != nil { return hc.Conn.Close()
return err
}
return nil
} }
func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) { 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 package vmess
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"math/rand" "math/rand"
@ -12,7 +13,7 @@ import (
type httpConn struct { type httpConn struct {
net.Conn net.Conn
cfg *HTTPConfig cfg *HTTPConfig
rhandshake bool reader *bufio.Reader
whandshake bool whandshake bool
} }
@ -25,8 +26,8 @@ type HTTPConfig struct {
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
func (hc *httpConn) Read(b []byte) (int, error) { func (hc *httpConn) Read(b []byte) (int, error) {
if hc.rhandshake { if hc.reader != nil {
n, err := hc.Conn.Read(b) n, err := hc.reader.Read(b)
return n, err return n, err
} }
@ -40,8 +41,8 @@ func (hc *httpConn) Read(b []byte) (int, error) {
return 0, err return 0, err
} }
hc.rhandshake = true hc.reader = reader.R
return hc.Conn.Read(b) return reader.R.Read(b)
} }
// Write implements io.Writer. // Write implements io.Writer.

View File

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

View File

@ -66,7 +66,7 @@ func Init(dir string) error {
if err != nil { if err != nil {
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) 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() f.Close()
} }

View File

@ -69,8 +69,21 @@ type PacketConn interface {
type ProxyAdapter interface { type ProxyAdapter interface {
Name() string Name() string
Type() AdapterType 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) 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) DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
DialUDP(metadata *Metadata) (PacketConn, error) DialUDP(metadata *Metadata) (PacketConn, error)
SupportUDP() bool SupportUDP() bool
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns" 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 // a default ip dns
ip = net.ParseIP(c.host) ip = net.ParseIP(c.host)
} else { } else {
var err error if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
if ip, err = c.r.ResolveIP(c.host); err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err) return nil, fmt.Errorf("use default dns resolve failed: %w", err)
} }
} }

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -83,12 +84,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return nil, err return nil, err
} }
ip, err := r.ResolveIPv4(host) ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil { if err != nil {
return nil, err 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) { 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 { for _, client := range clients {
r := client r := client
fast.Go(func() (interface{}, error) { fast.Go(func() (interface{}, error) {

20
go.mod
View File

@ -3,20 +3,20 @@ module github.com/Dreamacro/clash
go 1.16 go 1.16
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.6 github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/chi/v5 v5.0.2
github.com/go-chi/cors v1.1.1 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.38 github.com/miekg/dns v1.1.41
github.com/oschwald/geoip2-golang v1.4.0 github.com/oschwald/geoip2-golang v1.5.0
github.com/sirupsen/logrus v1.8.0 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.7.0
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )

68
go.sum
View File

@ -1,71 +1,57 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI= github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= 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.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 v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi/v5 v5.0.2 h1:4xKeALZdMEsuI5s05PU2Bm89Uc5iM04qFubUCl5LfAQ=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.0.2/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= 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=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= 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 h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
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/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw= github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 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-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df h1:y7QZzfUiTwWam+xBn29Ulb8CBwVN5UdzmMDavl9Whlw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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-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-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 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/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=
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 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=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

View File

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"net/url" "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 // 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" P "github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
) )

View File

@ -8,10 +8,10 @@ import (
"time" "time"
"github.com/Dreamacro/clash/tunnel/statistic" "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/go-chi/render"
"github.com/gorilla/websocket"
) )
func connectionRouter() http.Handler { func connectionRouter() http.Handler {

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import (
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic" "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/cors"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"

View File

@ -45,7 +45,7 @@ func init() {
func main() { func main() {
if version { 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 return
} }

View File

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

View File

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

View File

@ -75,5 +75,5 @@ func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *ne
lAddr: lAddr, lAddr: lAddr,
buf: buf, 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) return strings.EqualFold(cached.(string), ps.process)
} }
func (p *Process) Adapter() string { func (ps *Process) Adapter() string {
return p.adapter return ps.adapter
} }
func (p *Process) Payload() string { func (ps *Process) Payload() string {
return p.process return ps.process
} }
func (p *Process) ShouldResolveIP() bool { func (ps *Process) ShouldResolveIP() bool {
return false return false
} }

View File

@ -20,11 +20,12 @@ import (
func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) { func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
req := ctx.Request() req := ctx.Request()
conn := ctx.Conn() conn := ctx.Conn()
host := req.Host
inboundReader := bufio.NewReader(conn) inboundReader := bufio.NewReader(conn)
outboundReader := bufio.NewReader(outbound) outboundReader := bufio.NewReader(outbound)
inbound.RemoveExtraHTTPHostPort(req)
host := req.Host
for { for {
keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
@ -53,7 +54,12 @@ func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
goto handleResponse 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("Proxy-Connection", "keep-alive")
resp.Header.Set("Connection", "keep-alive") resp.Header.Set("Connection", "keep-alive")
resp.Header.Set("Keep-Alive", "timeout=4") resp.Header.Set("Keep-Alive", "timeout=4")
@ -79,6 +85,7 @@ func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) {
break break
} }
inbound.RemoveExtraHTTPHostPort(req)
// Sometimes firefox just open a socket to process multiple domains in HTTP // Sometimes firefox just open a socket to process multiple domains in HTTP
// The temporary solution is close connection when encountering different HOST // The temporary solution is close connection when encountering different HOST
if req.Host != host { if req.Host != host {
@ -104,9 +111,14 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
return errors.New("udp addr invalid") return errors.New("udp addr invalid")
} }
_, err := pc.WriteTo(packet.Data(), addr) if _, err := pc.WriteTo(packet.Data(), addr); err != nil {
return err return err
} }
// reset timeout
pc.SetReadDeadline(time.Now().Add(udpTimeout))
return nil
}
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
buf := pool.Get(pool.RelayBufferSize) buf := pool.Get(pool.RelayBufferSize)

View File

@ -121,6 +121,12 @@ func preHandleMetadata(metadata *C.Metadata) error {
// handle IP string on host // handle IP string on host
if ip := net.ParseIP(metadata.Host); ip != nil { if ip := net.ParseIP(metadata.Host); ip != nil {
metadata.DstIP = ip metadata.DstIP = ip
metadata.Host = ""
if ip.To4() != nil {
metadata.AddrType = C.AtypIPv4
} else {
metadata.AddrType = C.AtypIPv6
}
} }
// preprocess enhanced-mode metadata // preprocess enhanced-mode metadata
@ -329,7 +335,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
} }
if metadata.NetWork == C.UDP && !adapter.SupportUDP() { 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 continue
} }
return adapter, rule, nil return adapter, rule, nil