Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
baf03b81e3 | |||
9807e1189c | |||
3d5a0d9f73 | |||
cc96187f58 | |||
3aefa1d924 | |||
42e21b3733 | |||
0a35237915 | |||
a1f3a5ea26 | |||
e63f995258 | |||
d0c829c578 | |||
4ad9761b32 | |||
1f593d37fb | |||
109bfcb0f9 | |||
7ee49f5171 | |||
d759d16944 | |||
807d53c1e7 | |||
1355196b7c | |||
573316bcde | |||
784c28266c | |||
5da1b2a8aa | |||
0976d27cb1 | |||
6c83ff3496 | |||
f7f97ef625 | |||
5acdd72a1d | |||
f53686103d | |||
f63c9eb22f | |||
a37243cf30 |
3
.github/workflows/go.yml
vendored
3
.github/workflows/go.yml
vendored
@ -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
|
|
||||||
|
@ -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~~
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
250
component/gun/gun.go
Normal 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
38
component/gun/leb128.go
Normal 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
|
||||||
|
}
|
@ -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{
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
103
component/vmess/header.go
Normal 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()
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
20
go.mod
@ -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
68
go.sum
@ -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=
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
2
main.go
2
main.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user