Compare commits

...

35 Commits

Author SHA1 Message Date
8c7c8f4374 Chore: update dependencies 2022-07-07 22:15:50 +08:00
65a8e8f59c Fix: process rule type (#2206) 2022-07-06 13:44:04 +08:00
5497adaba1 Fix: fakeip udp should not replace with another ip 2022-07-05 21:09:29 +08:00
aaf08dadff Change: remove AddrType on Metadata (#2199) 2022-07-05 20:26:43 +08:00
557297ac9a Chore: load balance hash need to have fallback strategy 2022-07-04 21:36:33 +08:00
77a1e3a653 Chore: cleanup bind mark code 2022-06-30 17:27:57 +08:00
27e1d6cdae Chore: cleanup code 2022-06-30 17:12:06 +08:00
91c22b16bf Fix: proxy provider filter validation (#2198) 2022-06-30 17:08:53 +08:00
fc5c9b931b Fix: try to unmap lAddr on tproxy udp listener 2022-06-29 23:36:45 +08:00
c231fd1466 Chore: update dependencies 2022-06-19 13:01:43 +08:00
fbb27b84d1 Chore: add redir-host deprecated warnning 2022-06-14 11:26:04 +08:00
e0c5a85314 Fix: missing import 2022-06-12 21:22:02 +08:00
2fa1a5c4b9 Chore: update tproxy udp packet read logic 2022-06-12 19:37:51 +08:00
06d75da257 Chore: adjust Relay copy memory alloc logic 2022-06-11 20:38:16 +08:00
09d49bac95 Chore: embed shadowsocks2 2022-06-01 21:43:20 +08:00
3360839fe3 Chore: make CodeQL happy 2022-06-01 21:38:05 +08:00
c1285adbf8 Feature: can set custom interface for dns nameserver (#2126) 2022-06-01 10:50:54 +08:00
9d2fc976e2 Chore: upgrade to yaml v3 2022-05-26 17:47:05 +08:00
7f41f94fff Fix: benchmark read bytes 2022-05-23 12:58:18 +08:00
d1f0dac302 Fix: test broken on opensource repo 2022-05-23 12:30:54 +08:00
afb3e00067 Chore: add benchmark r/w 2022-05-23 12:27:52 +08:00
9a31ad6151 Chore: cleanup test go.mod 2022-05-21 17:46:34 +08:00
09cc6b69e3 Chore: cleanup test code 2022-05-21 17:38:17 +08:00
8603ac40a1 Chore: make linter happy 2022-05-17 19:58:33 +08:00
b384449717 Fix: fix upgrade header detect (#2134) 2022-05-15 09:12:53 +08:00
da7ffc0da9 Fix: add length check for ssr auth_aes128_sha1 (#2129) 2022-05-13 11:21:39 +08:00
5dd94c8298 Chore: update dependencies 2022-05-07 21:08:15 +08:00
412b44a981 Fix: decode nil value in slice decoder (#2102) 2022-05-07 11:00:58 +08:00
aef4dd3fe7 Fix: make log api unblocked 2022-04-26 22:36:10 +08:00
6a92c6af4e Fix: http proxy Upgrade behavior (#2097) 2022-04-25 19:50:20 +08:00
e010940b61 Improve: replace bootstrap dns (#2080) 2022-04-16 15:31:26 +08:00
2c9a4d276a Chore: add more github action cache 2022-04-14 23:37:41 +08:00
4dfba73e5c Fix: SyscallN should not use nargs 2022-04-14 23:37:19 +08:00
c282d662ca Fix: make golangci lint support multi GOOS 2022-04-13 17:51:21 +08:00
b3d7594813 Chore: add none alias to dummy on ShadowsocksR (#2056) 2022-04-13 10:06:06 +08:00
71 changed files with 1732 additions and 1859 deletions

View File

@ -49,6 +49,8 @@ jobs:
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
push: true push: true
tags: 'dreamacro/clash:dev,ghcr.io/dreamacro/clash:dev' tags: 'dreamacro/clash:dev,ghcr.io/dreamacro/clash:dev'
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Get all docker tags - name: Get all docker tags
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
@ -74,3 +76,5 @@ jobs:
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
push: true push: true
tags: ${{steps.tags.outputs.result}} tags: ${{steps.tags.outputs.result}}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -20,7 +20,9 @@ jobs:
- name: Cache go module - name: Cache go module
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go- ${{ runner.os }}-go-

View File

@ -130,7 +130,11 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases) releases: $(gz_releases) $(zip_releases)
lint: lint:
golangci-lint run ./... GOOS=darwin golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=linux golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
GOOS=openbsd golangci-lint run ./...
clean: clean:
rm $(BINDIR)/* rm $(BINDIR)/*

View File

@ -184,10 +184,9 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
} }
addr = C.Metadata{ addr = C.Metadata{
AddrType: C.AtypDomainName, Host: u.Hostname(),
Host: u.Hostname(), DstIP: nil,
DstIP: nil, DstPort: port,
DstPort: port,
} }
return return
} }

View File

@ -11,9 +11,7 @@ import (
) )
func parseSocksAddr(target socks5.Addr) *C.Metadata { func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{ metadata := &C.Metadata{}
AddrType: int(target[0]),
}
switch target[0] { switch target[0] {
case socks5.AtypDomainName: case socks5.AtypDomainName:
@ -44,21 +42,13 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
host = strings.TrimRight(host, ".") host = strings.TrimRight(host, ".")
metadata := &C.Metadata{ metadata := &C.Metadata{
NetWork: C.TCP, NetWork: C.TCP,
AddrType: C.AtypDomainName, Host: host,
Host: host, DstIP: nil,
DstIP: nil, DstPort: port,
DstPort: port,
} }
ip := net.ParseIP(host) if ip := net.ParseIP(host); ip != nil {
if ip != nil {
switch {
case ip.To4() == nil:
metadata.AddrType = C.AtypIPv6
default:
metadata.AddrType = C.AtypIPv4
}
metadata.DstIP = ip metadata.DstIP = ip
} }

View File

@ -10,11 +10,10 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
"github.com/Dreamacro/go-shadowsocks2/core"
) )
type ShadowSocks struct { type ShadowSocks struct {

View File

@ -8,12 +8,11 @@ import (
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
"github.com/Dreamacro/clash/transport/ssr/obfs" "github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol" "github.com/Dreamacro/clash/transport/ssr/protocol"
"github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
) )
type ShadowSocksR struct { type ShadowSocksR struct {
@ -92,6 +91,12 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
} }
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility
// https://github.com/Dreamacro/clash/pull/2056
if option.Cipher == "none" {
option.Cipher = "dummy"
}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
password := option.Password password := option.Password
@ -103,13 +108,14 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
ivSize int ivSize int
key []byte key []byte
) )
if option.Cipher == "dummy" { if option.Cipher == "dummy" {
ivSize = 0 ivSize = 0
key = core.Kdf(option.Password, 16) key = core.Kdf(option.Password, 16)
} else { } else {
ciph, ok := coreCiph.(*core.StreamCipher) ciph, ok := coreCiph.(*core.StreamCipher)
if !ok { if !ok {
return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher) return nil, fmt.Errorf("%s is not none or a supported stream cipher in ssr", cipher)
} }
ivSize = ciph.IVSize() ivSize = ciph.IVSize()
key = ciph.Key key = ciph.Key

View File

@ -20,10 +20,11 @@ func tcpKeepAlive(c net.Conn) {
func serializesSocksAddr(metadata *C.Metadata) []byte { func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte var buf [][]byte
aType := uint8(metadata.AddrType) addrType := metadata.AddrType()
aType := uint8(addrType)
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16) p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
port := []byte{uint8(p >> 8), uint8(p & 0xff)} port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType { switch addrType {
case socks5.AtypDomainName: case socks5.AtypDomainName:
len := uint8(len(metadata.Host)) len := uint8(len(metadata.Host))
host := []byte(metadata.Host) host := []byte(metadata.Host)

View File

@ -14,6 +14,7 @@ import (
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@ -327,16 +328,16 @@ func NewVmess(option VmessOption) (*Vmess, error) {
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
var addrType byte var addrType byte
var addr []byte var addr []byte
switch metadata.AddrType { switch metadata.AddrType() {
case C.AtypIPv4: case socks5.AtypIPv4:
addrType = byte(vmess.AtypIPv4) addrType = byte(vmess.AtypIPv4)
addr = make([]byte, net.IPv4len) addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.To4()) copy(addr[:], metadata.DstIP.To4())
case C.AtypIPv6: case socks5.AtypIPv6:
addrType = byte(vmess.AtypIPv6) addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len) addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.To16()) copy(addr[:], metadata.DstIP.To16())
case C.AtypDomainName: case socks5.AtypDomainName:
addrType = byte(vmess.AtypDomainName) addrType = byte(vmess.AtypDomainName)
addr = make([]byte, len(metadata.Host)+1) addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host)) addr[0] = byte(len(metadata.Host))

View File

@ -30,10 +30,8 @@ type LoadBalance struct {
var errStrategy = errors.New("unsupported strategy") var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]any) string { func parseStrategy(config map[string]any) string {
if elm, ok := config["strategy"]; ok { if strategy, ok := config["strategy"].(string); ok {
if strategy, ok := elm.(string); ok { return strategy
return strategy
}
} }
return "consistent-hashing" return "consistent-hashing"
} }
@ -129,6 +127,13 @@ func strategyConsistentHashing() strategyFn {
} }
} }
// when availability is poor, traverse the entire list to get the available nodes
for _, proxy := range proxies {
if proxy.Alive() {
return proxy
}
}
return proxies[0] return proxies[0]
} }
} }

View File

@ -125,10 +125,8 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{} opts := []urlTestOption{}
// tolerance // tolerance
if elm, ok := config["tolerance"]; ok { if tolerance, ok := config["tolerance"].(int); ok {
if tolerance, ok := elm.(int); ok { opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
}
} }
return opts return opts

View File

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

View File

@ -12,7 +12,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
const ( const (
@ -129,7 +129,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
proxies := []C.Proxy{} proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { if name, ok := mapping["name"].(string); ok && len(filter) > 0 && !filterReg.MatchString(name) {
continue continue
} }
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)

24
common/net/relay.go Normal file
View File

@ -0,0 +1,24 @@
package net
import (
"io"
"net"
"time"
)
// Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
rightConn.SetReadDeadline(time.Now())
<-ch
}

View File

@ -52,8 +52,8 @@ func (alloc *Allocator) Put(buf []byte) error {
return errors.New("allocator Put() incorrect buffer size") return errors.New("allocator Put() incorrect buffer size")
} }
//lint:ignore SA6002 ignore temporarily
//nolint //nolint
//lint:ignore SA6002 ignore temporarily
alloc.buffers[bits].Put(buf) alloc.buffers[bits].Put(buf)
return nil return nil
} }

View File

@ -25,7 +25,6 @@ type Result struct {
} }
// Do single.Do likes sync.singleFlight // Do single.Do likes sync.singleFlight
//lint:ignore ST1008 it likes sync.singleFlight
func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) { func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) {
s.mux.Lock() s.mux.Lock()
now := time.Now() now := time.Now()

View File

@ -159,9 +159,19 @@ func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
for valSlice.Len() <= i { for valSlice.Len() <= i {
valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
} }
currentField := valSlice.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i) fieldName := fmt.Sprintf("%s[%d]", name, i)
if currentData == nil {
// in weakly type mode, null will convert to zero value
if d.option.WeaklyTypedInput {
continue
}
// in non-weakly type mode, null will convert to nil if element's zero value is nil, otherwise return an error
if elemKind := valElemType.Kind(); elemKind == reflect.Map || elemKind == reflect.Slice {
continue
}
return fmt.Errorf("'%s' can not be null", fieldName)
}
currentField := valSlice.Index(i)
if err := d.decode(fieldName, currentData, currentField); err != nil { if err := d.decode(fieldName, currentData, currentField); err != nil {
return err return err
} }

View File

@ -137,3 +137,45 @@ func TestStructure_Nest(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, s.BazOptional, goal) assert.Equal(t, s.BazOptional, goal)
} }
func TestStructure_SliceNilValue(t *testing.T) {
rawMap := map[string]any{
"foo": 1,
"bar": []any{"bar", nil},
}
goal := &BazSlice{
Foo: 1,
Bar: []string{"bar", ""},
}
s := &BazSlice{}
err := weakTypeDecoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, goal.Bar, s.Bar)
s = &BazSlice{}
err = decoder.Decode(rawMap, s)
assert.NotNil(t, err)
}
func TestStructure_SliceNilValueComplex(t *testing.T) {
rawMap := map[string]any{
"bar": []any{map[string]any{"bar": "foo"}, nil},
}
s := &struct {
Bar []map[string]any `test:"bar"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Nil(t, s.Bar[1])
ss := &struct {
Bar []Baz `test:"bar"`
}{}
err = decoder.Decode(rawMap, ss)
assert.NotNil(t, err)
}

View File

@ -31,13 +31,13 @@ func bindMarkToControl(mark int, chain controlFn) controlFn {
} }
} }
return c.Control(func(fd uintptr) { var innerErr error
switch network { err = c.Control(func(fd uintptr) {
case "tcp4", "udp4": innerErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
case "tcp6", "udp6":
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
}
}) })
if innerErr != nil {
err = innerErr
}
return
} }
} }

View File

@ -174,7 +174,7 @@ func newSearcher(isV4, isTCP bool) *searcher {
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) { func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
for size, buf := uint32(8), make([]byte, 8); ; { for size, buf := uint32(8), make([]byte, 8); ; {
ptr := unsafe.Pointer(&buf[0]) ptr := unsafe.Pointer(&buf[0])
err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
switch err { switch err {
case 0: case 0:
@ -209,13 +209,13 @@ func getExecPathFromPID(pid uint32) (string, error) {
buf := make([]uint16, syscall.MAX_LONG_PATH) buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(len(buf)) size := uint32(len(buf))
r1, _, err := syscall.Syscall6( r1, _, err := syscall.SyscallN(
queryProcName, 4, queryProcName,
uintptr(h), uintptr(h),
uintptr(1), uintptr(1),
uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)), uintptr(unsafe.Pointer(&size)),
0, 0) )
if r1 == 0 { if r1 == 0 {
return "", err return "", err
} }

View File

@ -0,0 +1,12 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
package resolver
import _ "unsafe"
//go:linkname defaultNS net.defaultNS
var defaultNS []string
func init() {
defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"}
}

View File

@ -22,7 +22,7 @@ import (
R "github.com/Dreamacro/clash/rule" R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// General config // General config
@ -477,6 +477,10 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
// parse with specific interface
// .e.g 10.0.0.1#en0
interfaceName := u.Fragment
var addr, dnsNetType string var addr, dnsNetType string
switch u.Scheme { switch u.Scheme {
case "udp": case "udp":
@ -506,8 +510,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers = append( nameservers = append(
nameservers, nameservers,
dns.NameServer{ dns.NameServer{
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
Interface: interfaceName,
}, },
) )
} }

View File

@ -4,14 +4,12 @@ import (
"encoding/json" "encoding/json"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/transport/socks5"
) )
// Socks addr type // Socks addr type
const ( const (
AtypIPv4 = 1
AtypDomainName = 3
AtypIPv6 = 4
TCP NetWork = iota TCP NetWork = iota
UDP UDP
@ -69,7 +67,6 @@ type Metadata struct {
DstIP net.IP `json:"destinationIP"` DstIP net.IP `json:"destinationIP"`
SrcPort string `json:"sourcePort"` SrcPort string `json:"sourcePort"`
DstPort string `json:"destinationPort"` DstPort string `json:"destinationPort"`
AddrType int `json:"-"`
Host string `json:"host"` Host string `json:"host"`
DNSMode DNSMode `json:"dnsMode"` DNSMode DNSMode `json:"dnsMode"`
ProcessPath string `json:"processPath"` ProcessPath string `json:"processPath"`
@ -83,6 +80,17 @@ func (m *Metadata) SourceAddress() string {
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort) return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
} }
func (m *Metadata) AddrType() int {
switch true {
case m.Host != "" || m.DstIP == nil:
return socks5.AtypDomainName
case m.DstIP.To4() != nil:
return socks5.AtypIPv4
default:
return socks5.AtypIPv6
}
}
func (m *Metadata) Resolved() bool { func (m *Metadata) Resolved() bool {
return m.DstIP != nil return m.DstIP != nil
} }
@ -93,11 +101,6 @@ func (m *Metadata) Pure() *Metadata {
if m.DNSMode == DNSMapping && m.DstIP != nil { if m.DNSMode == DNSMapping && m.DstIP != nil {
copy := *m copy := *m
copy.Host = "" copy.Host = ""
if copy.DstIP.To4() != nil {
copy.AddrType = AtypIPv4
} else {
copy.AddrType = AtypIPv6
}
return &copy return &copy
} }

View File

@ -79,7 +79,7 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
return msg, err return msg, err
} }
func newDoHClient(url string, r *Resolver) *dohClient { func newDoHClient(url, iface string, r *Resolver) *dohClient {
return &dohClient{ return &dohClient{
url: url, url: url,
transport: &http.Transport{ transport: &http.Transport{
@ -95,7 +95,12 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return nil, err return nil, err
} }
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port)) options := []dialer.Option{}
if iface != "" {
options = append(options, dialer.WithInterface(iface))
}
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port), options...)
}, },
}, },
} }

View File

@ -51,7 +51,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
for _, s := range servers { for _, s := range servers {
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver)) ret = append(ret, newDoHClient(s.Addr, s.Interface, resolver))
continue continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))

27
go.mod
View File

@ -3,35 +3,34 @@ module github.com/Dreamacro/clash
go 1.18 go 1.18
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.0 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.2.0+incompatible github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
github.com/miekg/dns v1.1.47 github.com/miekg/dns v1.1.50
github.com/oschwald/geoip2-golang v1.6.1 github.com/oschwald/geoip2-golang v1.7.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.8.0
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.9.0 go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.4.0 go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/net v0.0.0-20220706163947-c90051bbdb60
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/oschwald/maxminddb-golang v1.8.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
golang.org/x/mod v0.4.2 // indirect golang.org/x/mod v0.4.2 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
) )

65
go.sum
View File

@ -1,13 +1,12 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/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.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
@ -21,18 +20,16 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd h1:efcJu2Vzz6DoSq245deWNzTz6l/gsqdphm3FjmI88/g= github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo=
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
@ -40,26 +37,27 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/miekg/dns v1.1.47 h1:J9bWiXbqMbnZPcY8Qi2E3EWIBsIm6MZzzJB9VRg5gL8= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/oschwald/geoip2-golang v1.6.1 h1:GKxT3yaWWNXSb7vj6D7eoJBns+lGYgx08QO0UcNm0YY= github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
github.com/oschwald/geoip2-golang v1.6.1/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s= github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk= github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY=
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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@ -67,14 +65,13 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0= go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -87,14 +84,14 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -104,7 +101,6 @@ golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -116,8 +112,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -136,9 +132,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -130,6 +130,11 @@ func updateDNS(c *config.DNS) {
Policy: c.NameServerPolicy, Policy: c.NameServerPolicy,
} }
// deprecated warnning
if cfg.EnhancedMode == C.DNSMapping {
log.Warnln("[DNS] %s is deprecated, please use %s instead", cfg.EnhancedMode.String(), C.DNSFakeIP.String())
}
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)

View File

@ -208,16 +208,27 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusOK) render.Status(r, http.StatusOK)
} }
ch := make(chan log.Event, 1024)
sub := log.Subscribe() sub := log.Subscribe()
defer log.UnSubscribe(sub) defer log.UnSubscribe(sub)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
var err error
for elm := range sub { go func() {
buf.Reset() for elm := range sub {
log := elm.(*log.Event) log := elm.(log.Event)
select {
case ch <- log:
default:
}
}
close(ch)
}()
for log := range ch {
if log.LogLevel < level { if log.LogLevel < level {
continue continue
} }
buf.Reset()
if err := json.NewEncoder(buf).Encode(Log{ if err := json.NewEncoder(buf).Encode(Log{
Type: log.Type(), Type: log.Type(),
@ -226,6 +237,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
break break
} }
var err error
if wsConn == nil { if wsConn == nil {
_, err = w.Write(buf.Bytes()) _, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush() w.(http.Flusher).Flush()

View File

@ -19,12 +19,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
client := newClient(c.RemoteAddr(), in) client := newClient(c.RemoteAddr(), in)
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
var conn *N.BufferedConn conn := N.NewBufferedConn(c)
if bufConn, ok := c.(*N.BufferedConn); ok {
conn = bufConn
} else {
conn = N.NewBufferedConn(c)
}
keepAlive := true keepAlive := true
trusted := cache == nil // disable authenticate if cache is nil trusted := cache == nil // disable authenticate if cache is nil
@ -66,6 +61,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
request.RequestURI = "" request.RequestURI = ""
if isUpgradeRequest(request) {
handleUpgrade(conn, request, in)
return // hijack connection
}
removeHopByHopHeaders(request.Header) removeHopByHopHeaders(request.Header)
removeExtraHTTPHostPort(request) removeExtraHTTPHostPort(request)

69
listener/http/upgrade.go Normal file
View File

@ -0,0 +1,69 @@
package http
import (
"net"
"net/http"
"strings"
"github.com/Dreamacro/clash/adapter/inbound"
N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
func isUpgradeRequest(req *http.Request) bool {
for _, header := range req.Header["Connection"] {
for _, elm := range strings.Split(header, ",") {
if strings.EqualFold(strings.TrimSpace(elm), "Upgrade") {
return true
}
}
}
return false
}
func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext) {
defer conn.Close()
removeProxyHeaders(request.Header)
removeExtraHTTPHostPort(request)
address := request.Host
if _, _, err := net.SplitHostPort(address); err != nil {
address = net.JoinHostPort(address, "80")
}
dstAddr := socks5.ParseAddr(address)
if dstAddr == nil {
return
}
left, right := net.Pipe()
in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right)
bufferedLeft := N.NewBufferedConn(left)
defer bufferedLeft.Close()
err := request.Write(bufferedLeft)
if err != nil {
return
}
resp, err := http.ReadResponse(bufferedLeft.Reader(), request)
if err != nil {
return
}
removeProxyHeaders(resp.Header)
err = resp.Write(conn)
if err != nil {
return
}
if resp.StatusCode == http.StatusSwitchingProtocols {
N.Relay(bufferedLeft, conn)
}
}

View File

@ -8,15 +8,21 @@ import (
"strings" "strings"
) )
// removeHopByHopHeaders remove Proxy-* headers
func removeProxyHeaders(header http.Header) {
header.Del("Proxy-Connection")
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
}
// removeHopByHopHeaders remove hop-by-hop header // removeHopByHopHeaders remove hop-by-hop header
func removeHopByHopHeaders(header http.Header) { func removeHopByHopHeaders(header http.Header) {
// Strip hop-by-hop header based on RFC: // Strip hop-by-hop header based on RFC:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
header.Del("Proxy-Connection") removeProxyHeaders(header)
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
header.Del("TE") header.Del("TE")
header.Del("Trailers") header.Del("Trailers")
header.Del("Transfer-Encoding") header.Del("Transfer-Encoding")

View File

@ -2,12 +2,13 @@ package tproxy
import ( import (
"net" "net"
"net/netip"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
) )
type packet struct { type packet struct {
lAddr *net.UDPAddr lAddr netip.AddrPort
buf []byte buf []byte
} }
@ -17,7 +18,7 @@ func (c *packet) Data() []byte {
// WriteBack opens a new socket binding `addr` to write UDP packet back // WriteBack opens a new socket binding `addr` to write UDP packet back
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr)
if err != nil { if err != nil {
n = 0 n = 0
return return
@ -29,7 +30,7 @@ func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
// LocalAddr returns the source IP/Port of UDP Packet // LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr { func (c *packet) LocalAddr() net.Addr {
return c.lAddr return &net.UDPAddr{IP: c.lAddr.Addr().AsSlice(), Port: int(c.lAddr.Port()), Zone: c.lAddr.Addr().Zone()}
} }
func (c *packet) Drop() { func (c *packet) Drop() {

View File

@ -2,6 +2,7 @@ package tproxy
import ( import (
"net" "net"
"net/netip"
"github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
@ -58,7 +59,7 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
oob := make([]byte, 1024) oob := make([]byte, 1024)
for { for {
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) n, oobn, _, lAddr, err := c.ReadMsgUDPAddrPort(buf, oob)
if err != nil { if err != nil {
pool.Put(buf) pool.Put(buf)
if rl.closed { if rl.closed {
@ -67,19 +68,24 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
continue continue
} }
rAddr, err := getOrigDst(oob, oobn) rAddr, err := getOrigDst(oob[:oobn])
if err != nil { if err != nil {
continue continue
} }
handlePacketConn(l, in, buf[:n], lAddr, rAddr)
if rAddr.Addr().Is4() {
// try to unmap 4in6 address
lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port())
}
handlePacketConn(in, buf[:n], lAddr, rAddr)
} }
}() }()
return rl, nil return rl, nil
} }
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { func handlePacketConn(in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) {
target := socks5.ParseAddrToSocksAddr(rAddr) target := socks5.AddrFromStdAddrPort(rAddr)
pkt := &packet{ pkt := &packet{
lAddr: lAddr, lAddr: lAddr,
buf: buf, buf: buf,

View File

@ -3,13 +3,14 @@
package tproxy package tproxy
import ( import (
"encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip"
"os" "os"
"strconv" "strconv"
"syscall" "syscall"
"golang.org/x/sys/unix"
) )
const ( const (
@ -19,7 +20,7 @@ const (
// dialUDP acts like net.DialUDP for transparent proxy. // dialUDP acts like net.DialUDP for transparent proxy.
// It binds to a non-local address(`lAddr`). // It binds to a non-local address(`lAddr`).
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err error) {
rSockAddr, err := udpAddrToSockAddr(rAddr) rSockAddr, err := udpAddrToSockAddr(rAddr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,23 +36,25 @@ func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPCo
return nil, err return nil, err
} }
defer func() {
if err != nil {
syscall.Close(fd)
}
}()
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
if err = syscall.Bind(fd, lSockAddr); err != nil { if err = syscall.Bind(fd, lSockAddr); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
if err = syscall.Connect(fd, rSockAddr); err != nil { if err = syscall.Connect(fd, rSockAddr); err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
@ -60,35 +63,26 @@ func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPCo
c, err := net.FileConn(fdFile) c, err := net.FileConn(fdFile)
if err != nil { if err != nil {
syscall.Close(fd)
return nil, err return nil, err
} }
return c.(*net.UDPConn), nil return c.(*net.UDPConn), nil
} }
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { func udpAddrToSockAddr(addr netip.AddrPort) (syscall.Sockaddr, error) {
switch { if addr.Addr().Is4() {
case addr.IP.To4() != nil: return &syscall.SockaddrInet4{Addr: addr.Addr().As4(), Port: int(addr.Port())}, nil
ip := [4]byte{}
copy(ip[:], addr.IP.To4())
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
default:
ip := [16]byte{}
copy(ip[:], addr.IP.To16())
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
if err != nil {
zoneID = 0
}
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
} }
zoneID, err := strconv.ParseUint(addr.Addr().Zone(), 10, 32)
if err != nil {
zoneID = 0
}
return &syscall.SockaddrInet6{Addr: addr.Addr().As16(), Port: int(addr.Port()), ZoneId: uint32(zoneID)}, nil
} }
func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int {
switch net[len(net)-1] { switch net[len(net)-1] {
case '4': case '4':
return syscall.AF_INET return syscall.AF_INET
@ -96,29 +90,35 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
return syscall.AF_INET6 return syscall.AF_INET6
} }
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { if lAddr.Addr().Is4() && rAddr.Addr().Is4() {
return syscall.AF_INET return syscall.AF_INET
} }
return syscall.AF_INET6 return syscall.AF_INET6
} }
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { func getOrigDst(oob []byte) (netip.AddrPort, error) {
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) // oob contains socket control messages which we need to parse.
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil { if err != nil {
return nil, err return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err)
} }
for _, msg := range msgs { // retrieve the destination address from the SCM.
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { sa, err := unix.ParseOrigDstAddr(&scms[0])
ip := net.IP(msg.Data[4:8]) if err != nil {
port := binary.BigEndian.Uint16(msg.Data[2:4]) return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err)
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
ip := net.IP(msg.Data[8:24])
port := binary.BigEndian.Uint16(msg.Data[2:4])
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
}
} }
return nil, errors.New("cannot find origDst") // encode the destination address into a cmsg.
var rAddr netip.AddrPort
switch v := sa.(type) {
case *unix.SockaddrInet4:
rAddr = netip.AddrPortFrom(netip.AddrFrom4(v.Addr), uint16(v.Port))
case *unix.SockaddrInet6:
rAddr = netip.AddrPortFrom(netip.AddrFrom16(v.Addr), uint16(v.Port))
default:
return netip.AddrPort{}, fmt.Errorf("unsupported address type: %T", v)
}
return rAddr, nil
} }

View File

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

View File

@ -74,7 +74,7 @@ func SetLevel(newLevel LogLevel) {
level = newLevel level = newLevel
} }
func print(data *Event) { func print(data Event) {
if data.LogLevel < level { if data.LogLevel < level {
return return
} }
@ -91,8 +91,8 @@ func print(data *Event) {
} }
} }
func newLog(logLevel LogLevel, format string, v ...any) *Event { func newLog(logLevel LogLevel, format string, v ...any) Event {
return &Event{ return Event{
LogLevel: logLevel, LogLevel: logLevel,
Payload: fmt.Sprintf(format, v...), Payload: fmt.Sprintf(format, v...),
} }

View File

@ -16,9 +16,6 @@ func (d *Domain) RuleType() C.RuleType {
} }
func (d *Domain) Match(metadata *C.Metadata) bool { func (d *Domain) Match(metadata *C.Metadata) bool {
if metadata.AddrType != C.AtypDomainName {
return false
}
return metadata.Host == d.domain return metadata.Host == d.domain
} }

View File

@ -16,11 +16,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
} }
func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { func (dk *DomainKeyword) Match(metadata *C.Metadata) bool {
if metadata.AddrType != C.AtypDomainName { return strings.Contains(metadata.Host, dk.keyword)
return false
}
domain := metadata.Host
return strings.Contains(domain, dk.keyword)
} }
func (dk *DomainKeyword) Adapter() string { func (dk *DomainKeyword) Adapter() string {

View File

@ -16,9 +16,6 @@ func (ds *DomainSuffix) RuleType() C.RuleType {
} }
func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { func (ds *DomainSuffix) Match(metadata *C.Metadata) bool {
if metadata.AddrType != C.AtypDomainName {
return false
}
domain := metadata.Host domain := metadata.Host
return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix
} }

View File

@ -14,7 +14,11 @@ type Process struct {
} }
func (ps *Process) RuleType() C.RuleType { func (ps *Process) RuleType() C.RuleType {
return C.Process if ps.nameOnly {
return C.Process
}
return C.ProcessPath
} }
func (ps *Process) Match(metadata *C.Metadata) bool { func (ps *Process) Match(metadata *C.Metadata) bool {

16
test/.golangci.yaml Normal file
View File

@ -0,0 +1,16 @@
linters:
disable-all: true
enable:
- gofumpt
- govet
- gci
- staticcheck
linters-settings:
gci:
sections:
- standard
- prefix(github.com/Dreamacro/clash)
- default
staticcheck:
go: '1.18'

View File

@ -1,8 +1,9 @@
lint: lint:
golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./... GOOS=darwin golangci-lint run ./...
GOOS=linux golangci-lint run ./...
test: test:
go test -p 1 -v ./... go test -p 1 -v ./...
benchmark: benchmark:
go test -benchmem -run=^$ -bench . go test -benchmem -run=^$$ -bench .

View File

@ -24,6 +24,7 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
const ( const (
@ -52,13 +53,10 @@ var (
{HostPort: "10002", HostIP: "0.0.0.0"}, {HostPort: "10002", HostIP: "0.0.0.0"},
}, },
} }
isDarwin = runtime.GOOS == "darwin"
) )
func init() { func init() {
if runtime.GOOS == "darwin" {
isDarwin = true
}
currentDir, err := os.Getwd() currentDir, err := os.Getwd()
if err != nil { if err != nil {
panic(err) panic(err)
@ -110,6 +108,7 @@ func init() {
continue continue
} }
println("pulling image:", image)
imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{}) imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{})
if err != nil { if err != nil {
panic(err) panic(err)
@ -214,46 +213,35 @@ func testPingPongWithSocksPort(t *testing.T, port int) {
pingCh, pongCh, test := newPingPongPair() pingCh, pongCh, test := newPingPongPair()
go func() { go func() {
l, err := Listen("tcp", ":10001") l, err := Listen("tcp", ":10001")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer l.Close() defer l.Close()
c, err := l.Accept() c, err := l.Accept()
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
buf := make([]byte, 4) buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil { _, err = io.ReadFull(c, buf)
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
pingCh <- buf pingCh <- buf
if _, err := c.Write([]byte("pong")); err != nil { _, err = c.Write([]byte("pong"))
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
}() }()
go func() { go func() {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer c.Close() defer c.Close()
if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil { _, err = socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil)
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
if _, err := c.Write([]byte("ping")); err != nil { _, err = c.Write([]byte("ping"))
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
buf := make([]byte, 4) buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil { _, err = io.ReadFull(c, buf)
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
pongCh <- buf pongCh <- buf
}() }()
@ -304,9 +292,7 @@ func testPingPongWithConn(t *testing.T, c net.Conn) error {
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error { func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
l, err := ListenPacket("udp", ":10001") l, err := ListenPacket("udp", ":10001")
if err != nil { require.NoError(t, err)
return err
}
defer l.Close() defer l.Close()
rAddr := &net.UDPAddr{IP: localIP, Port: 10001} rAddr := &net.UDPAddr{IP: localIP, Port: 10001}
@ -349,9 +335,7 @@ type hashPair struct {
func testLargeDataWithConn(t *testing.T, c net.Conn) error { func testLargeDataWithConn(t *testing.T, c net.Conn) error {
l, err := Listen("tcp", ":10001") l, err := Listen("tcp", ":10001")
if err != nil { require.NoError(t, err)
return err
}
defer l.Close() defer l.Close()
times := 100 times := 100
@ -443,9 +427,7 @@ func testLargeDataWithConn(t *testing.T, c net.Conn) error {
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error { func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
l, err := ListenPacket("udp", ":10001") l, err := ListenPacket("udp", ":10001")
if err != nil { require.NoError(t, err)
return err
}
defer l.Close() defer l.Close()
rAddr := &net.UDPAddr{IP: localIP, Port: 10001} rAddr := &net.UDPAddr{IP: localIP, Port: 10001}
@ -541,7 +523,7 @@ func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error { func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300)) err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
assert.NoError(t, err) require.NoError(t, err)
errCh := make(chan error, 1) errCh := make(chan error, 1)
go func() { go func() {
@ -564,9 +546,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001", DstPort: "10001",
AddrType: socks5.AtypDomainName, AddrType: socks5.AtypDomainName,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer conn.Close() defer conn.Close()
assert.NoError(t, testPingPongWithConn(t, conn)) assert.NoError(t, testPingPongWithConn(t, conn))
@ -575,9 +555,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001", DstPort: "10001",
AddrType: socks5.AtypDomainName, AddrType: socks5.AtypDomainName,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer conn.Close() defer conn.Close()
assert.NoError(t, testLargeDataWithConn(t, conn)) assert.NoError(t, testLargeDataWithConn(t, conn))
@ -591,9 +569,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001", DstPort: "10001",
AddrType: socks5.AtypIPv4, AddrType: socks5.AtypIPv4,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer pc.Close() defer pc.Close()
assert.NoError(t, testPingPongWithPacketConn(t, pc)) assert.NoError(t, testPingPongWithPacketConn(t, pc))
@ -604,9 +580,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001", DstPort: "10001",
AddrType: socks5.AtypIPv4, AddrType: socks5.AtypIPv4,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer pc.Close() defer pc.Close()
assert.NoError(t, testLargeDataWithPacketConn(t, pc)) assert.NoError(t, testLargeDataWithPacketConn(t, pc))
@ -617,9 +591,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001", DstPort: "10001",
AddrType: socks5.AtypIPv4, AddrType: socks5.AtypIPv4,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
defer pc.Close() defer pc.Close()
assert.NoError(t, testPacketConnTimeout(t, pc)) assert.NoError(t, testPacketConnTimeout(t, pc))
@ -627,40 +599,55 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) { func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
l, err := Listen("tcp", ":10001") l, err := Listen("tcp", ":10001")
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
defer l.Close() defer l.Close()
go func() {
c, err := l.Accept()
if err != nil {
assert.FailNow(b, err.Error())
}
defer c.Close()
io.Copy(io.Discard, c)
}()
chunkSize := int64(16 * 1024) chunkSize := int64(16 * 1024)
chunk := make([]byte, chunkSize) chunk := make([]byte, chunkSize)
rand.Read(chunk) rand.Read(chunk)
go func() {
c, err := l.Accept()
if err != nil {
return
}
defer c.Close()
go func() {
for {
_, err := c.Write(chunk)
if err != nil {
return
}
}
}()
io.Copy(io.Discard, c)
}()
conn, err := proxy.DialContext(context.Background(), &C.Metadata{ conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(), Host: localIP.String(),
DstPort: "10001", DstPort: "10001",
AddrType: socks5.AtypDomainName, AddrType: socks5.AtypDomainName,
}) })
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
b.SetBytes(chunkSize) _, err = conn.Write([]byte("skip protocol handshake"))
b.ResetTimer() require.NoError(b, err)
for i := 0; i < b.N; i++ {
if _, err := conn.Write(chunk); err != nil { b.Run("Write", func(b *testing.B) {
assert.FailNow(b, err.Error()) b.SetBytes(chunkSize)
for i := 0; i < b.N; i++ {
conn.Write(chunk)
} }
} })
b.Run("Read", func(b *testing.B) {
b.SetBytes(chunkSize)
buf := make([]byte, chunkSize)
for i := 0; i < b.N; i++ {
io.ReadFull(conn, buf)
}
})
} }
func TestClash_Basic(t *testing.T) { func TestClash_Basic(t *testing.T) {
@ -669,12 +656,11 @@ mixed-port: 10000
log-level: silent log-level: silent
` `
if err := parseAndApply(basic); err != nil { err := parseAndApply(basic)
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
defer cleanup() defer cleanup()
time.Sleep(waitTime) require.True(t, TCPing(net.JoinHostPort(localIP.String(), "10000")))
testPingPongWithSocksPort(t, 10000) testPingPongWithSocksPort(t, 10000)
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func exchange(address, domain string, tp uint16) ([]dns.RR, error) { func exchange(address, domain string, tp uint16) ([]dns.RR, error) {
@ -30,18 +31,15 @@ dns:
- 119.29.29.29 - 119.29.29.29
` `
if err := parseAndApply(basic); err != nil { err := parseAndApply(basic)
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
defer cleanup() defer cleanup()
time.Sleep(waitTime) time.Sleep(waitTime)
rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA) rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA)
assert.NoError(t, err) assert.NoError(t, err)
if !assert.NotEmpty(t, rr) { assert.NotEmptyf(t, rr, "record empty")
assert.FailNow(t, "record empty")
}
record := rr[0].(*dns.A) record := rr[0].(*dns.A)
assert.Equal(t, record.A.String(), "1.1.1.1") assert.Equal(t, record.A.String(), "1.1.1.1")
@ -68,9 +66,8 @@ dns:
- 119.29.29.29 - 119.29.29.29
` `
if err := parseAndApply(basic); err != nil { err := parseAndApply(basic)
assert.FailNow(t, err.Error()) require.NoError(t, err)
}
defer cleanup() defer cleanup()
time.Sleep(waitTime) time.Sleep(waitTime)

View File

@ -8,8 +8,6 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
) )
var isDarwin = false
func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) { func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) {
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {

View File

@ -3,52 +3,47 @@ module clash-test
go 1.18 go 1.18
require ( require (
github.com/Dreamacro/clash v1.7.2-0.20211108085948-bd2ea2b917aa github.com/Dreamacro/clash v1.10.6
github.com/docker/docker v20.10.13+incompatible github.com/docker/docker v20.10.17+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/miekg/dns v1.1.47 github.com/miekg/dns v1.1.49
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.7.2
golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
) )
replace github.com/Dreamacro/clash => ../ replace github.com/Dreamacro/clash => ../
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/containerd/containerd v1.6.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd // indirect github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/oschwald/geoip2-golang v1.6.1 // indirect github.com/oschwald/geoip2-golang v1.7.0 // indirect
github.com/oschwald/maxminddb-golang v1.8.0 // indirect github.com/oschwald/maxminddb-golang v1.9.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/bbolt v1.3.6 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/mod v0.4.2 // indirect golang.org/x/mod v0.4.2 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
google.golang.org/grpc v1.45.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gotest.tools/v3 v3.1.0 // indirect gotest.tools/v3 v3.1.0 // indirect
) )

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
func TestClash_SnellObfsHTTP(t *testing.T) { func TestClash_SnellObfsHTTP(t *testing.T) {
@ -24,9 +24,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "snell-http") id, err := startContainer(cfg, hostCfg, "snell-http")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -41,9 +39,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) {
"mode": "http", "mode": "http",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -61,9 +57,7 @@ func TestClash_SnellObfsTLS(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "snell-tls") id, err := startContainer(cfg, hostCfg, "snell-tls")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -78,9 +72,7 @@ func TestClash_SnellObfsTLS(t *testing.T) {
"mode": "tls", "mode": "tls",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -98,9 +90,7 @@ func TestClash_Snell(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "snell") id, err := startContainer(cfg, hostCfg, "snell")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -112,9 +102,7 @@ func TestClash_Snell(t *testing.T) {
Port: 10002, Port: 10002,
Psk: "password", Psk: "password",
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -132,9 +120,7 @@ func TestClash_Snellv3(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "snell") id, err := startContainer(cfg, hostCfg, "snell")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -148,9 +134,7 @@ func TestClash_Snellv3(t *testing.T) {
UDP: true, UDP: true,
Version: 3, Version: 3,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -167,10 +151,8 @@ func Benchmark_Snell(b *testing.B) {
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))}, Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))},
} }
id, err := startContainer(cfg, hostCfg, "snell-http") id, err := startContainer(cfg, hostCfg, "snell-bench")
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
b.Cleanup(func() { b.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -185,9 +167,7 @@ func Benchmark_Snell(b *testing.B) {
"mode": "http", "mode": "http",
}, },
}) })
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
benchmarkProxy(b, proxy) benchmarkProxy(b, proxy)

View File

@ -1,13 +1,14 @@
package main package main
import ( import (
"net"
"testing" "testing"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
func TestClash_Shadowsocks(t *testing.T) { func TestClash_Shadowsocks(t *testing.T) {
@ -22,9 +23,7 @@ func TestClash_Shadowsocks(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "ss") id, err := startContainer(cfg, hostCfg, "ss")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -38,9 +37,7 @@ func TestClash_Shadowsocks(t *testing.T) {
Cipher: "chacha20-ietf-poly1305", Cipher: "chacha20-ietf-poly1305",
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -60,9 +57,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "ss-obfs-http") id, err := startContainer(cfg, hostCfg, "ss-obfs-http")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -80,9 +75,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) {
"mode": "http", "mode": "http",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -102,9 +95,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "ss-obfs-tls") id, err := startContainer(cfg, hostCfg, "ss-obfs-tls")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -122,9 +113,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) {
"mode": "tls", "mode": "tls",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -144,9 +133,7 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin") id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -164,9 +151,7 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
"mode": "websocket", "mode": "websocket",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -183,10 +168,8 @@ func Benchmark_Shadowsocks(b *testing.B) {
PortBindings: defaultPortBindings, PortBindings: defaultPortBindings,
} }
id, err := startContainer(cfg, hostCfg, "ss") id, err := startContainer(cfg, hostCfg, "ss-bench")
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
b.Cleanup(func() { b.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -200,10 +183,8 @@ func Benchmark_Shadowsocks(b *testing.B) {
Cipher: "aes-256-gcm", Cipher: "aes-256-gcm",
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
time.Sleep(waitTime) require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
benchmarkProxy(b, proxy) benchmarkProxy(b, proxy)
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"net"
"testing" "testing"
"time" "time"
@ -9,7 +10,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
func TestClash_Trojan(t *testing.T) { func TestClash_Trojan(t *testing.T) {
@ -27,9 +28,7 @@ func TestClash_Trojan(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "trojan") id, err := startContainer(cfg, hostCfg, "trojan")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -44,9 +43,7 @@ func TestClash_Trojan(t *testing.T) {
SkipCertVerify: true, SkipCertVerify: true,
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -67,10 +64,10 @@ func TestClash_TrojanGrpc(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "trojan-grpc") id, err := startContainer(cfg, hostCfg, "trojan-grpc")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewTrojan(outbound.TrojanOption{ proxy, err := outbound.NewTrojan(outbound.TrojanOption{
Name: "trojan", Name: "trojan",
@ -85,9 +82,7 @@ func TestClash_TrojanGrpc(t *testing.T) {
GrpcServiceName: "example", GrpcServiceName: "example",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -108,10 +103,10 @@ func TestClash_TrojanWebsocket(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "trojan-ws") id, err := startContainer(cfg, hostCfg, "trojan-ws")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewTrojan(outbound.TrojanOption{ proxy, err := outbound.NewTrojan(outbound.TrojanOption{
Name: "trojan", Name: "trojan",
@ -123,9 +118,7 @@ func TestClash_TrojanWebsocket(t *testing.T) {
UDP: true, UDP: true,
Network: "ws", Network: "ws",
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -145,10 +138,8 @@ func Benchmark_Trojan(b *testing.B) {
}, },
} }
id, err := startContainer(cfg, hostCfg, "trojan") id, err := startContainer(cfg, hostCfg, "trojan-bench")
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
b.Cleanup(func() { b.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -163,10 +154,8 @@ func Benchmark_Trojan(b *testing.B) {
SkipCertVerify: true, SkipCertVerify: true,
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
time.Sleep(waitTime) require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
benchmarkProxy(b, proxy) benchmarkProxy(b, proxy)
} }

View File

@ -35,3 +35,16 @@ func ListenPacket(network, address string) (net.PacketConn, error) {
} }
return nil, lastErr return nil, lastErr
} }
func TCPing(addr string) bool {
for i := 0; i < 10; i++ {
conn, err := net.Dial("tcp", addr)
if err == nil {
conn.Close()
return true
}
time.Sleep(time.Millisecond * 500)
}
return false
}

View File

@ -9,7 +9,7 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
func TestClash_Vmess(t *testing.T) { func TestClash_Vmess(t *testing.T) {
@ -25,9 +25,7 @@ func TestClash_Vmess(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess") id, err := startContainer(cfg, hostCfg, "vmess")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
t.Cleanup(func() { t.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -41,9 +39,7 @@ func TestClash_Vmess(t *testing.T) {
Cipher: "auto", Cipher: "auto",
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -64,10 +60,10 @@ func TestClash_VmessTLS(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-tls") id, err := startContainer(cfg, hostCfg, "vmess-tls")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -80,9 +76,7 @@ func TestClash_VmessTLS(t *testing.T) {
ServerName: "example.org", ServerName: "example.org",
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -103,10 +97,10 @@ func TestClash_VmessHTTP2(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-http2") id, err := startContainer(cfg, hostCfg, "vmess-http2")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -124,9 +118,7 @@ func TestClash_VmessHTTP2(t *testing.T) {
Path: "/test", Path: "/test",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -145,10 +137,10 @@ func TestClash_VmessHTTP(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-http") id, err := startContainer(cfg, hostCfg, "vmess-http")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -176,9 +168,7 @@ func TestClash_VmessHTTP(t *testing.T) {
}, },
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -197,10 +187,10 @@ func TestClash_VmessWebsocket(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-ws") id, err := startContainer(cfg, hostCfg, "vmess-ws")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -211,9 +201,7 @@ func TestClash_VmessWebsocket(t *testing.T) {
Network: "ws", Network: "ws",
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -234,10 +222,10 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-ws") id, err := startContainer(cfg, hostCfg, "vmess-ws")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -250,9 +238,7 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
SkipCertVerify: true, SkipCertVerify: true,
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -273,10 +259,10 @@ func TestClash_VmessGrpc(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-grpc") id, err := startContainer(cfg, hostCfg, "vmess-grpc")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -293,9 +279,7 @@ func TestClash_VmessGrpc(t *testing.T) {
GrpcServiceName: "example!", GrpcServiceName: "example!",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -314,10 +298,10 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt") id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -333,9 +317,7 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
EarlyDataHeaderName: "Sec-WebSocket-Protocol", EarlyDataHeaderName: "Sec-WebSocket-Protocol",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
@ -354,10 +336,10 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
} }
id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt") id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt")
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error()) t.Cleanup(func() {
} cleanContainer(id)
defer cleanContainer(id) })
proxy, err := outbound.NewVmess(outbound.VmessOption{ proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess", Name: "vmess",
@ -372,16 +354,14 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
Path: "/?ed=2048", Path: "/?ed=2048",
}, },
}) })
if err != nil { require.NoError(t, err)
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
testSuit(t, proxy) testSuit(t, proxy)
} }
func Benchmark_Vmess(b *testing.B) { func Benchmark_Vmess(b *testing.B) {
configPath := C.Path.Resolve("vmess-aead.json") configPath := C.Path.Resolve("vmess.json")
cfg := &container.Config{ cfg := &container.Config{
Image: ImageVmess, Image: ImageVmess,
@ -392,10 +372,8 @@ func Benchmark_Vmess(b *testing.B) {
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)}, Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
} }
id, err := startContainer(cfg, hostCfg, "vmess-aead") id, err := startContainer(cfg, hostCfg, "vmess-bench")
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
b.Cleanup(func() { b.Cleanup(func() {
cleanContainer(id) cleanContainer(id)
@ -410,9 +388,7 @@ func Benchmark_Vmess(b *testing.B) {
AlterID: 0, AlterID: 0,
UDP: true, UDP: true,
}) })
if err != nil { require.NoError(b, err)
assert.FailNow(b, err.Error())
}
time.Sleep(waitTime) time.Sleep(waitTime)
benchmarkProxy(b, proxy) benchmarkProxy(b, proxy)

View File

@ -0,0 +1,5 @@
## Embedded go-shadowsocks2
from https://github.com/Dreamacro/go-shadowsocks2
origin https://github.com/riobard/go-shadowsocks2

View File

@ -0,0 +1,164 @@
package core
import (
"crypto/md5"
"errors"
"net"
"sort"
"strings"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
)
type Cipher interface {
StreamConnCipher
PacketConnCipher
}
type StreamConnCipher interface {
StreamConn(net.Conn) net.Conn
}
type PacketConnCipher interface {
PacketConn(net.PacketConn) net.PacketConn
}
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
var ErrCipherNotSupported = errors.New("cipher not supported")
const (
aeadAes128Gcm = "AEAD_AES_128_GCM"
aeadAes192Gcm = "AEAD_AES_192_GCM"
aeadAes256Gcm = "AEAD_AES_256_GCM"
aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305"
aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305"
)
// List of AEAD ciphers: key size in bytes and constructor
var aeadList = map[string]struct {
KeySize int
New func([]byte) (shadowaead.Cipher, error)
}{
aeadAes128Gcm: {16, shadowaead.AESGCM},
aeadAes192Gcm: {24, shadowaead.AESGCM},
aeadAes256Gcm: {32, shadowaead.AESGCM},
aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305},
aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305},
}
// List of stream ciphers: key size in bytes and constructor
var streamList = map[string]struct {
KeySize int
New func(key []byte) (shadowstream.Cipher, error)
}{
"RC4-MD5": {16, shadowstream.RC4MD5},
"AES-128-CTR": {16, shadowstream.AESCTR},
"AES-192-CTR": {24, shadowstream.AESCTR},
"AES-256-CTR": {32, shadowstream.AESCTR},
"AES-128-CFB": {16, shadowstream.AESCFB},
"AES-192-CFB": {24, shadowstream.AESCFB},
"AES-256-CFB": {32, shadowstream.AESCFB},
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
"XCHACHA20": {32, shadowstream.Xchacha20},
}
// ListCipher returns a list of available cipher names sorted alphabetically.
func ListCipher() []string {
var l []string
for k := range aeadList {
l = append(l, k)
}
for k := range streamList {
l = append(l, k)
}
sort.Strings(l)
return l
}
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
func PickCipher(name string, key []byte, password string) (Cipher, error) {
name = strings.ToUpper(name)
switch name {
case "DUMMY":
return &dummy{}, nil
case "CHACHA20-IETF-POLY1305":
name = aeadChacha20Poly1305
case "XCHACHA20-IETF-POLY1305":
name = aeadXChacha20Poly1305
case "AES-128-GCM":
name = aeadAes128Gcm
case "AES-192-GCM":
name = aeadAes192Gcm
case "AES-256-GCM":
name = aeadAes256Gcm
}
if choice, ok := aeadList[name]; ok {
if len(key) == 0 {
key = Kdf(password, choice.KeySize)
}
if len(key) != choice.KeySize {
return nil, shadowaead.KeySizeError(choice.KeySize)
}
aead, err := choice.New(key)
return &AeadCipher{Cipher: aead, Key: key}, err
}
if choice, ok := streamList[name]; ok {
if len(key) == 0 {
key = Kdf(password, choice.KeySize)
}
if len(key) != choice.KeySize {
return nil, shadowstream.KeySizeError(choice.KeySize)
}
ciph, err := choice.New(key)
return &StreamCipher{Cipher: ciph, Key: key}, err
}
return nil, ErrCipherNotSupported
}
type AeadCipher struct {
shadowaead.Cipher
Key []byte
}
func (aead *AeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
func (aead *AeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
return shadowaead.NewPacketConn(c, aead)
}
type StreamCipher struct {
shadowstream.Cipher
Key []byte
}
func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
func (ciph *StreamCipher) PacketConn(c net.PacketConn) net.PacketConn {
return shadowstream.NewPacketConn(c, ciph)
}
// dummy cipher does not encrypt
type dummy struct{}
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
// key-derivation function from original Shadowsocks
func Kdf(password string, keyLen int) []byte {
var b, prev []byte
h := md5.New()
for len(b) < keyLen {
h.Write(prev)
h.Write([]byte(password))
b = h.Sum(b)
prev = b[len(b)-h.Size():]
h.Reset()
}
return b[:keyLen]
}

View File

@ -0,0 +1,94 @@
package shadowaead
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"io"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
type Cipher interface {
KeySize() int
SaltSize() int
Encrypter(salt []byte) (cipher.AEAD, error)
Decrypter(salt []byte) (cipher.AEAD, error)
}
type KeySizeError int
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
func hkdfSHA1(secret, salt, info, outkey []byte) {
r := hkdf.New(sha1.New, secret, salt, info)
if _, err := io.ReadFull(r, outkey); err != nil {
panic(err) // should never happen
}
}
type metaCipher struct {
psk []byte
makeAEAD func(key []byte) (cipher.AEAD, error)
}
func (a *metaCipher) KeySize() int { return len(a.psk) }
func (a *metaCipher) SaltSize() int {
if ks := a.KeySize(); ks > 16 {
return ks
}
return 16
}
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
subkey := make([]byte, a.KeySize())
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
return a.makeAEAD(subkey)
}
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
subkey := make([]byte, a.KeySize())
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
return a.makeAEAD(subkey)
}
func aesGCM(key []byte) (cipher.AEAD, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(blk)
}
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
func AESGCM(psk []byte) (Cipher, error) {
switch l := len(psk); l {
case 16, 24, 32: // AES 128/196/256
default:
return nil, aes.KeySizeError(l)
}
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
}
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
// must be 32.
func Chacha20Poly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
}
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
// must be 32.
func XChacha20Poly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
}

View File

@ -0,0 +1,95 @@
package shadowaead
import (
"crypto/rand"
"errors"
"io"
"net"
"github.com/Dreamacro/clash/common/pool"
)
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
var ErrShortPacket = errors.New("short packet")
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
// Pack encrypts plaintext using Cipher with a randomly generated salt and
// returns a slice of dst containing the encrypted packet and any error occurred.
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
saltSize := ciph.SaltSize()
salt := dst[:saltSize]
if _, err := rand.Read(salt); err != nil {
return nil, err
}
aead, err := ciph.Encrypter(salt)
if err != nil {
return nil, err
}
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
return nil, io.ErrShortBuffer
}
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
return dst[:saltSize+len(b)], nil
}
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
saltSize := ciph.SaltSize()
if len(pkt) < saltSize {
return nil, ErrShortPacket
}
salt := pkt[:saltSize]
aead, err := ciph.Decrypter(salt)
if err != nil {
return nil, err
}
if len(pkt) < saltSize+aead.Overhead() {
return nil, ErrShortPacket
}
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
return nil, io.ErrShortBuffer
}
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
return b, err
}
type PacketConn struct {
net.PacketConn
Cipher
}
const maxPacketSize = 64 * 1024
// NewPacketConn wraps a net.PacketConn with cipher
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
return &PacketConn{PacketConn: c, Cipher: ciph}
}
// WriteTo encrypts b and write to addr using the embedded PacketConn.
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.Get(maxPacketSize)
defer pool.Put(buf)
buf, err := Pack(buf, b, c)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
// ReadFrom reads from the embedded PacketConn and decrypts into b.
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
if err != nil {
return n, addr, err
}
copy(b, bb)
return len(bb), addr, err
}

View File

@ -0,0 +1,285 @@
package shadowaead
import (
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"net"
"github.com/Dreamacro/clash/common/pool"
)
const (
// payloadSizeMask is the maximum size of payload in bytes.
payloadSizeMask = 0x3FFF // 16*1024 - 1
bufSize = 17 * 1024 // >= 2+aead.Overhead()+payloadSizeMask+aead.Overhead()
)
var ErrZeroChunk = errors.New("zero chunk")
type Writer struct {
io.Writer
cipher.AEAD
nonce [32]byte // should be sufficient for most nonce sizes
}
// NewWriter wraps an io.Writer with authenticated encryption.
func NewWriter(w io.Writer, aead cipher.AEAD) *Writer { return &Writer{Writer: w, AEAD: aead} }
// Write encrypts p and writes to the embedded io.Writer.
func (w *Writer) Write(p []byte) (n int, err error) {
buf := pool.Get(bufSize)
defer pool.Put(buf)
nonce := w.nonce[:w.NonceSize()]
tag := w.Overhead()
off := 2 + tag
// compatible with snell
if len(p) == 0 {
buf = buf[:off]
buf[0], buf[1] = byte(0), byte(0)
w.Seal(buf[:0], nonce, buf[:2], nil)
increment(nonce)
_, err = w.Writer.Write(buf)
return
}
for nr := 0; n < len(p) && err == nil; n += nr {
nr = payloadSizeMask
if n+nr > len(p) {
nr = len(p) - n
}
buf = buf[:off+nr+tag]
buf[0], buf[1] = byte(nr>>8), byte(nr) // big-endian payload size
w.Seal(buf[:0], nonce, buf[:2], nil)
increment(nonce)
w.Seal(buf[:off], nonce, p[n:n+nr], nil)
increment(nonce)
_, err = w.Writer.Write(buf)
}
return
}
// ReadFrom reads from the given io.Reader until EOF or error, encrypts and
// writes to the embedded io.Writer. Returns number of bytes read from r and
// any error encountered.
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
buf := pool.Get(bufSize)
defer pool.Put(buf)
nonce := w.nonce[:w.NonceSize()]
tag := w.Overhead()
off := 2 + tag
for {
nr, er := r.Read(buf[off : off+payloadSizeMask])
n += int64(nr)
buf[0], buf[1] = byte(nr>>8), byte(nr)
w.Seal(buf[:0], nonce, buf[:2], nil)
increment(nonce)
w.Seal(buf[:off], nonce, buf[off:off+nr], nil)
increment(nonce)
if _, ew := w.Writer.Write(buf[:off+nr+tag]); ew != nil {
err = ew
return
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
err = er
}
return
}
}
}
type Reader struct {
io.Reader
cipher.AEAD
nonce [32]byte // should be sufficient for most nonce sizes
buf []byte // to be put back into bufPool
off int // offset to unconsumed part of buf
}
// NewReader wraps an io.Reader with authenticated decryption.
func NewReader(r io.Reader, aead cipher.AEAD) *Reader { return &Reader{Reader: r, AEAD: aead} }
// Read and decrypt a record into p. len(p) >= max payload size + AEAD overhead.
func (r *Reader) read(p []byte) (int, error) {
nonce := r.nonce[:r.NonceSize()]
tag := r.Overhead()
// decrypt payload size
p = p[:2+tag]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
_, err := r.Open(p[:0], nonce, p, nil)
increment(nonce)
if err != nil {
return 0, err
}
// decrypt payload
size := (int(p[0])<<8 + int(p[1])) & payloadSizeMask
if size == 0 {
return 0, ErrZeroChunk
}
p = p[:size+tag]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
_, err = r.Open(p[:0], nonce, p, nil)
increment(nonce)
if err != nil {
return 0, err
}
return size, nil
}
// Read reads from the embedded io.Reader, decrypts and writes to p.
func (r *Reader) Read(p []byte) (int, error) {
if r.buf == nil {
if len(p) >= payloadSizeMask+r.Overhead() {
return r.read(p)
}
b := pool.Get(bufSize)
n, err := r.read(b)
if err != nil {
return 0, err
}
r.buf = b[:n]
r.off = 0
}
n := copy(p, r.buf[r.off:])
r.off += n
if r.off == len(r.buf) {
pool.Put(r.buf[:cap(r.buf)])
r.buf = nil
}
return n, nil
}
// WriteTo reads from the embedded io.Reader, decrypts and writes to w until
// there's no more data to write or when an error occurs. Return number of
// bytes written to w and any error encountered.
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
if r.buf == nil {
r.buf = pool.Get(bufSize)
r.off = len(r.buf)
}
for {
for r.off < len(r.buf) {
nw, ew := w.Write(r.buf[r.off:])
r.off += nw
n += int64(nw)
if ew != nil {
if r.off == len(r.buf) {
pool.Put(r.buf[:cap(r.buf)])
r.buf = nil
}
err = ew
return
}
}
nr, er := r.read(r.buf)
if er != nil {
if er != io.EOF {
err = er
}
return
}
r.buf = r.buf[:nr]
r.off = 0
}
}
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
func increment(b []byte) {
for i := range b {
b[i]++
if b[i] != 0 {
return
}
}
}
type Conn struct {
net.Conn
Cipher
r *Reader
w *Writer
}
// NewConn wraps a stream-oriented net.Conn with cipher.
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
func (c *Conn) initReader() error {
salt := make([]byte, c.SaltSize())
if _, err := io.ReadFull(c.Conn, salt); err != nil {
return err
}
aead, err := c.Decrypter(salt)
if err != nil {
return err
}
c.r = NewReader(c.Conn, aead)
return nil
}
func (c *Conn) Read(b []byte) (int, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.Read(b)
}
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.WriteTo(w)
}
func (c *Conn) initWriter() error {
salt := make([]byte, c.SaltSize())
if _, err := rand.Read(salt); err != nil {
return err
}
aead, err := c.Encrypter(salt)
if err != nil {
return err
}
_, err = c.Conn.Write(salt)
if err != nil {
return err
}
c.w = NewWriter(c.Conn, aead)
return nil
}
func (c *Conn) Write(b []byte) (int, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.Write(b)
}
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.ReadFrom(r)
}

View File

@ -0,0 +1,116 @@
package shadowstream
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rc4"
"strconv"
"golang.org/x/crypto/chacha20"
)
// Cipher generates a pair of stream ciphers for encryption and decryption.
type Cipher interface {
IVSize() int
Encrypter(iv []byte) cipher.Stream
Decrypter(iv []byte) cipher.Stream
}
type KeySizeError int
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
// CTR mode
type ctrStream struct{ cipher.Block }
func (b *ctrStream) IVSize() int { return b.BlockSize() }
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
func AESCTR(key []byte) (Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &ctrStream{blk}, nil
}
// CFB mode
type cfbStream struct{ cipher.Block }
func (b *cfbStream) IVSize() int { return b.BlockSize() }
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
func AESCFB(key []byte) (Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &cfbStream{blk}, nil
}
// IETF-variant of chacha20
type chacha20ietfkey []byte
func (k chacha20ietfkey) IVSize() int { return chacha20.NonceSize }
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Chacha20IETF(key []byte) (Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, KeySizeError(chacha20.KeySize)
}
return chacha20ietfkey(key), nil
}
type xchacha20key []byte
func (k xchacha20key) IVSize() int { return chacha20.NonceSizeX }
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Xchacha20(key []byte) (Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, KeySizeError(chacha20.KeySize)
}
return xchacha20key(key), nil
}
type rc4Md5Key []byte
func (k rc4Md5Key) IVSize() int {
return 16
}
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
h := md5.New()
h.Write([]byte(k))
h.Write(iv)
rc4key := h.Sum(nil)
c, _ := rc4.NewCipher(rc4key)
return c
}
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream {
return k.Encrypter(iv)
}
func RC4MD5(key []byte) (Cipher, error) {
return rc4Md5Key(key), nil
}

View File

@ -0,0 +1,79 @@
package shadowstream
import (
"crypto/rand"
"errors"
"io"
"net"
"github.com/Dreamacro/clash/common/pool"
)
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
var ErrShortPacket = errors.New("short packet")
// Pack encrypts plaintext using stream cipher s and a random IV.
// Returns a slice of dst containing random IV and ciphertext.
// Ensure len(dst) >= s.IVSize() + len(plaintext).
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
if len(dst) < s.IVSize()+len(plaintext) {
return nil, io.ErrShortBuffer
}
iv := dst[:s.IVSize()]
_, err := rand.Read(iv)
if err != nil {
return nil, err
}
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
return dst[:len(iv)+len(plaintext)], nil
}
// Unpack decrypts pkt using stream cipher s.
// Returns a slice of dst containing decrypted plaintext.
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
if len(pkt) < s.IVSize() {
return nil, ErrShortPacket
}
if len(dst) < len(pkt)-s.IVSize() {
return nil, io.ErrShortBuffer
}
iv := pkt[:s.IVSize()]
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
return dst[:len(pkt)-len(iv)], nil
}
type PacketConn struct {
net.PacketConn
Cipher
}
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
return &PacketConn{PacketConn: c, Cipher: ciph}
}
const maxPacketSize = 64 * 1024
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.Get(maxPacketSize)
defer pool.Put(buf)
buf, err := Pack(buf, b, c.Cipher)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
if err != nil {
return n, addr, err
}
copy(b, bb)
return len(bb), addr, err
}

View File

@ -0,0 +1,197 @@
package shadowstream
import (
"crypto/cipher"
"crypto/rand"
"io"
"net"
)
const bufSize = 2048
type Writer struct {
io.Writer
cipher.Stream
buf [bufSize]byte
}
// NewWriter wraps an io.Writer with stream cipher encryption.
func NewWriter(w io.Writer, s cipher.Stream) *Writer { return &Writer{Writer: w, Stream: s} }
func (w *Writer) Write(p []byte) (n int, err error) {
buf := w.buf[:]
for nw := 0; n < len(p) && err == nil; n += nw {
end := n + len(buf)
if end > len(p) {
end = len(p)
}
w.XORKeyStream(buf, p[n:end])
nw, err = w.Writer.Write(buf[:end-n])
}
return
}
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
buf := w.buf[:]
for {
nr, er := r.Read(buf)
n += int64(nr)
b := buf[:nr]
w.XORKeyStream(b, b)
if _, err = w.Writer.Write(b); err != nil {
return
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
err = er
}
return
}
}
}
type Reader struct {
io.Reader
cipher.Stream
buf [bufSize]byte
}
// NewReader wraps an io.Reader with stream cipher decryption.
func NewReader(r io.Reader, s cipher.Stream) *Reader { return &Reader{Reader: r, Stream: s} }
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
if err != nil {
return 0, err
}
r.XORKeyStream(p, p[:n])
return
}
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
buf := r.buf[:]
for {
nr, er := r.Reader.Read(buf)
if nr > 0 {
r.XORKeyStream(buf, buf[:nr])
nw, ew := w.Write(buf[:nr])
n += int64(nw)
if ew != nil {
err = ew
return
}
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut)
err = er
}
return
}
}
}
// A Conn represents a Shadowsocks connection. It implements the net.Conn interface.
type Conn struct {
net.Conn
Cipher
r *Reader
w *Writer
readIV []byte
writeIV []byte
}
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
func (c *Conn) initReader() error {
if c.r == nil {
iv, err := c.ObtainReadIV()
if err != nil {
return err
}
c.r = NewReader(c.Conn, c.Decrypter(iv))
}
return nil
}
func (c *Conn) Read(b []byte) (int, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.Read(b)
}
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.WriteTo(w)
}
func (c *Conn) initWriter() error {
if c.w == nil {
iv, err := c.ObtainWriteIV()
if err != nil {
return err
}
if _, err := c.Conn.Write(iv); err != nil {
return err
}
c.w = NewWriter(c.Conn, c.Encrypter(iv))
}
return nil
}
func (c *Conn) Write(b []byte) (int, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.Write(b)
}
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.ReadFrom(r)
}
func (c *Conn) ObtainWriteIV() ([]byte, error) {
if len(c.writeIV) == c.IVSize() {
return c.writeIV, nil
}
iv := make([]byte, c.IVSize())
if _, err := rand.Read(iv); err != nil {
return nil, err
}
c.writeIV = iv
return iv, nil
}
func (c *Conn) ObtainReadIV() ([]byte, error) {
if len(c.readIV) == c.IVSize() {
return c.readIV, nil
}
iv := make([]byte, c.IVSize())
if _, err := io.ReadFull(c.Conn, iv); err != nil {
return nil, err
}
c.readIV = iv
return iv, nil
}

View File

@ -4,7 +4,8 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
) )

View File

@ -6,8 +6,7 @@ import (
"time" "time"
"github.com/Dreamacro/clash/component/pool" "github.com/Dreamacro/clash/component/pool"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
) )
type Pool struct { type Pool struct {

View File

@ -9,9 +9,8 @@ import (
"sync" "sync"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
) )
const ( const (

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
@ -398,6 +399,21 @@ func ParseAddrToSocksAddr(addr net.Addr) Addr {
return parsed return parsed
} }
func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr {
addr := addrPort.Addr()
if addr.Is4() {
ip4 := addr.As4()
return []byte{AtypIPv4, ip4[0], ip4[1], ip4[2], ip4[3], byte(addrPort.Port() >> 8), byte(addrPort.Port())}
}
buf := make([]byte, 1+net.IPv6len+2)
buf[0] = AtypIPv6
copy(buf[1:], addr.AsSlice())
buf[1+net.IPv6len] = byte(addrPort.Port() >> 8)
buf[1+net.IPv6len+1] = byte(addrPort.Port())
return buf
}
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) {
if len(packet) < 5 { if len(packet) < 5 {

View File

@ -154,6 +154,9 @@ func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error {
} }
func (a *authAES128) DecodePacket(b []byte) ([]byte, error) { func (a *authAES128) DecodePacket(b []byte) ([]byte, error) {
if len(b) < 4 {
return nil, errAuthAES128LengthError
}
if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) {
return nil, errAuthAES128ChksumError return nil, errAuthAES128ChksumError
} }

View File

@ -13,9 +13,8 @@ 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/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/ssr/tools" "github.com/Dreamacro/clash/transport/ssr/tools"
"github.com/Dreamacro/go-shadowsocks2/core"
) )
func init() { func init() {

View File

@ -12,8 +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/clash/transport/shadowsocks/core"
"github.com/Dreamacro/go-shadowsocks2/core"
) )
type Base struct { type Base struct {

View File

@ -2,28 +2,18 @@ package tunnel
import ( import (
"errors" "errors"
"io"
"net" "net"
"net/netip"
"time" "time"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
defer packet.Drop() defer packet.Drop()
// local resolve UDP dns
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return err
}
metadata.DstIP = ip
}
addr := metadata.UDPAddr() addr := metadata.UDPAddr()
if addr == nil { if addr == nil {
return errors.New("udp addr invalid") return errors.New("udp addr invalid")
@ -38,7 +28,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
return nil 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, oAddr, fAddr netip.Addr) {
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
defer pool.Put(buf) defer pool.Put(buf)
defer natTable.Delete(key) defer natTable.Delete(key)
@ -51,11 +41,16 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
return return
} }
if fAddr != nil { fromUDPAddr := from.(*net.UDPAddr)
from = fAddr if fAddr.IsValid() {
fromAddr, _ := netip.AddrFromSlice(fromUDPAddr.IP)
fromAddr.Unmap()
if oAddr == fromAddr {
fromUDPAddr.IP = fAddr.AsSlice()
}
} }
_, err = packet.WriteBack(buf[:n], from) _, err = packet.WriteBack(buf[:n], fromUDPAddr)
if err != nil { if err != nil {
return return
} }
@ -63,26 +58,5 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
} }
func handleSocket(ctx C.ConnContext, outbound net.Conn) { func handleSocket(ctx C.ConnContext, outbound net.Conn) {
relay(ctx.Conn(), outbound) N.Relay(ctx.Conn(), outbound)
}
// relay copies between left and right bidirectionally.
func relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
buf := pool.Get(pool.RelayBufferSize)
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(N.WriteOnlyWriter{Writer: leftConn}, N.ReadOnlyReader{Reader: rightConn}, buf)
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(N.WriteOnlyWriter{Writer: rightConn}, N.ReadOnlyReader{Reader: leftConn}, buf)
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"net/netip"
"runtime" "runtime"
"strconv" "strconv"
"sync" "sync"
@ -122,11 +123,6 @@ func preHandleMetadata(metadata *C.Metadata) error {
if ip := net.ParseIP(metadata.Host); ip != nil { if ip := net.ParseIP(metadata.Host); ip != nil {
metadata.DstIP = ip metadata.DstIP = ip
metadata.Host = "" metadata.Host = ""
if ip.To4() != nil {
metadata.AddrType = C.AtypIPv4
} else {
metadata.AddrType = C.AtypIPv6
}
} }
// preprocess enhanced-mode metadata // preprocess enhanced-mode metadata
@ -134,7 +130,6 @@ func preHandleMetadata(metadata *C.Metadata) error {
host, exist := resolver.FindHostByIP(metadata.DstIP) host, exist := resolver.FindHostByIP(metadata.DstIP)
if exist { if exist {
metadata.Host = host metadata.Host = host
metadata.AddrType = C.AtypDomainName
metadata.DNSMode = C.DNSMapping metadata.DNSMode = C.DNSMapping
if resolver.FakeIPEnabled() { if resolver.FakeIPEnabled() {
metadata.DstIP = nil metadata.DstIP = nil
@ -172,9 +167,10 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
} }
// make a fAddr if request ip is fakeip // make a fAddr if request ip is fakeip
var fAddr net.Addr var fAddr netip.Addr
if resolver.IsExistFakeIP(metadata.DstIP) { if resolver.IsExistFakeIP(metadata.DstIP) {
fAddr = metadata.UDPAddr() fAddr, _ = netip.AddrFromSlice(metadata.DstIP)
fAddr = fAddr.Unmap()
} }
if err := preHandleMetadata(metadata); err != nil { if err := preHandleMetadata(metadata); err != nil {
@ -182,6 +178,15 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
return return
} }
// local resolve UDP dns
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return
}
metadata.DstIP = ip
}
key := packet.LocalAddr().String() key := packet.LocalAddr().String()
handle := func() bool { handle := func() bool {
@ -246,7 +251,9 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
log.Infoln("[UDP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) log.Infoln("[UDP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress())
} }
go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) oAddr, _ := netip.AddrFromSlice(metadata.DstIP)
oAddr = oAddr.Unmap()
go handleUDPToLocal(packet.UDPPacket, pc, key, oAddr, fAddr)
natTable.Set(key, pc) natTable.Set(key, pc)
handle() handle()
@ -333,9 +340,9 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
if !processFound && rule.ShouldFindProcess() { if !processFound && rule.ShouldFindProcess() {
processFound = true processFound = true
srcPort, err := strconv.Atoi(metadata.SrcPort) srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
if err == nil { if err == nil {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
if err != nil { if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err) log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else { } else {