Update On Tue Sep 23 20:37:39 CEST 2025

This commit is contained in:
github-action[bot]
2025-09-23 20:37:39 +02:00
parent 6774a15632
commit 1f6f1f8a02
154 changed files with 23222 additions and 2903 deletions

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net"
"strconv"
"strings"
"sync"
CN "github.com/metacubex/mihomo/common/net"
@@ -30,8 +31,8 @@ type MieruOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
PortRange string `proxy:"port-range,omitempty"`
Port string `proxy:"port,omitempty"`
PortRange string `proxy:"port-range,omitempty"` // deprecated
Transport string `proxy:"transport"`
UDP bool `proxy:"udp,omitempty"`
UserName string `proxy:"username"`
@@ -123,13 +124,19 @@ func NewMieru(option MieruOption) (*Mieru, error) {
}
// Client is started lazily on the first use.
// Use the first port to construct the address.
var addr string
if option.Port != 0 {
addr = net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
var portStr string
if option.Port != "" {
portStr = option.Port
} else {
beginPort, _, _ := beginAndEndPortFromPortRange(option.PortRange)
addr = net.JoinHostPort(option.Server, strconv.Itoa(beginPort))
portStr = option.PortRange
}
firstPort, err := getFirstPort(portStr)
if err != nil {
return nil, fmt.Errorf("failed to get first port from port string %q: %w", portStr, err)
}
addr = net.JoinHostPort(option.Server, strconv.Itoa(firstPort))
outbound := &Mieru{
Base: &Base{
name: option.Name,
@@ -183,54 +190,62 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
}
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
var server *mierupb.ServerEndpoint
if net.ParseIP(option.Server) != nil {
// server is an IP address
if option.PortRange != "" {
server = &mierupb.ServerEndpoint{
IpAddress: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
PortRange: proto.String(option.PortRange),
portBindings := make([]*mierupb.PortBinding, 0)
if option.Port != "" {
parts := strings.Split(option.Port, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if strings.Contains(part, "-") {
_, _, err := beginAndEndPortFromPortRange(part)
if err == nil {
portBindings = append(portBindings, &mierupb.PortBinding{
PortRange: proto.String(part),
Protocol: transportProtocol,
},
},
}
} else {
server = &mierupb.ServerEndpoint{
IpAddress: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(int32(option.Port)),
Protocol: transportProtocol,
},
},
}
}
} else {
// server is a domain name
if option.PortRange != "" {
server = &mierupb.ServerEndpoint{
DomainName: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
PortRange: proto.String(option.PortRange),
Protocol: transportProtocol,
},
},
}
} else {
server = &mierupb.ServerEndpoint{
DomainName: proto.String(option.Server),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(int32(option.Port)),
Protocol: transportProtocol,
},
},
})
} else {
return nil, err
}
} else {
p, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("invalid port value: %s", part)
}
portBindings = append(portBindings, &mierupb.PortBinding{
Port: proto.Int32(int32(p)),
Protocol: transportProtocol,
})
}
}
}
if option.PortRange != "" {
parts := strings.Split(option.PortRange, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if _, _, err := beginAndEndPortFromPortRange(part); err == nil {
portBindings = append(portBindings, &mierupb.PortBinding{
PortRange: proto.String(part),
Protocol: transportProtocol,
})
}
}
}
var server *mierupb.ServerEndpoint
if net.ParseIP(option.Server) != nil {
// server is an IP address
server = &mierupb.ServerEndpoint{
IpAddress: proto.String(option.Server),
PortBindings: portBindings,
}
} else {
// server is a domain name
server = &mierupb.ServerEndpoint{
DomainName: proto.String(option.Server),
PortBindings: portBindings,
}
}
config := &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String(option.Name),
@@ -259,31 +274,9 @@ func validateMieruOption(option MieruOption) error {
if option.Server == "" {
return fmt.Errorf("server is empty")
}
if option.Port == 0 && option.PortRange == "" {
return fmt.Errorf("either port or port-range must be set")
if option.Port == "" && option.PortRange == "" {
return fmt.Errorf("port must be set")
}
if option.Port != 0 && option.PortRange != "" {
return fmt.Errorf("port and port-range cannot be set at the same time")
}
if option.Port != 0 && (option.Port < 1 || option.Port > 65535) {
return fmt.Errorf("port must be between 1 and 65535")
}
if option.PortRange != "" {
begin, end, err := beginAndEndPortFromPortRange(option.PortRange)
if err != nil {
return fmt.Errorf("invalid port-range format")
}
if begin < 1 || begin > 65535 {
return fmt.Errorf("begin port must be between 1 and 65535")
}
if end < 1 || end > 65535 {
return fmt.Errorf("end port must be between 1 and 65535")
}
if begin > end {
return fmt.Errorf("begin port must be less than or equal to end port")
}
}
if option.Transport != "TCP" {
return fmt.Errorf("transport must be TCP")
}
@@ -306,8 +299,36 @@ func validateMieruOption(option MieruOption) error {
return nil
}
func getFirstPort(portStr string) (int, error) {
if portStr == "" {
return 0, fmt.Errorf("port string is empty")
}
parts := strings.Split(portStr, ",")
firstPart := parts[0]
if strings.Contains(firstPart, "-") {
begin, _, err := beginAndEndPortFromPortRange(firstPart)
if err != nil {
return 0, err
}
return begin, nil
}
port, err := strconv.Atoi(firstPart)
if err != nil {
return 0, fmt.Errorf("invalid port format: %s", firstPart)
}
return port, nil
}
func beginAndEndPortFromPortRange(portRange string) (int, int, error) {
var begin, end int
_, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end)
if err != nil {
return 0, 0, fmt.Errorf("invalid port range format: %w", err)
}
if begin > end {
return 0, 0, fmt.Errorf("begin port is greater than end port: %s", portRange)
}
return begin, end, err
}

View File

@@ -1,22 +1,51 @@
package outbound
import "testing"
import (
"reflect"
"testing"
mieruclient "github.com/enfein/mieru/v3/apis/client"
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
"google.golang.org/protobuf/proto"
)
func TestNewMieru(t *testing.T) {
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
testCases := []struct {
option MieruOption
wantBaseAddr string
wantConfig *mieruclient.ClientConfig
}{
{
option: MieruOption{
Name: "test",
Server: "1.2.3.4",
Port: 10000,
Port: "10000",
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "1.2.3.4:10000",
wantConfig: &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String("test"),
User: &mierupb.User{
Name: proto.String("test"),
Password: proto.String("test"),
},
Servers: []*mierupb.ServerEndpoint{
{
IpAddress: proto.String("1.2.3.4"),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(10000),
Protocol: transportProtocol,
},
},
},
},
},
},
},
{
option: MieruOption{
@@ -28,28 +57,212 @@ func TestNewMieru(t *testing.T) {
Password: "test",
},
wantBaseAddr: "[2001:db8::1]:10001",
wantConfig: &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String("test"),
User: &mierupb.User{
Name: proto.String("test"),
Password: proto.String("test"),
},
Servers: []*mierupb.ServerEndpoint{
{
IpAddress: proto.String("2001:db8::1"),
PortBindings: []*mierupb.PortBinding{
{
PortRange: proto.String("10001-10002"),
Protocol: transportProtocol,
},
},
},
},
},
},
},
{
option: MieruOption{
Name: "test",
Server: "example.com",
Port: 10003,
Port: "10003",
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "example.com:10003",
wantConfig: &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String("test"),
User: &mierupb.User{
Name: proto.String("test"),
Password: proto.String("test"),
},
Servers: []*mierupb.ServerEndpoint{
{
DomainName: proto.String("example.com"),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(10003),
Protocol: transportProtocol,
},
},
},
},
},
},
},
{
option: MieruOption{
Name: "test",
Server: "example.com",
Port: "10004,10005",
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "example.com:10004",
wantConfig: &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String("test"),
User: &mierupb.User{
Name: proto.String("test"),
Password: proto.String("test"),
},
Servers: []*mierupb.ServerEndpoint{
{
DomainName: proto.String("example.com"),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(10004),
Protocol: transportProtocol,
},
{
Port: proto.Int32(10005),
Protocol: transportProtocol,
},
},
},
},
},
},
},
{
option: MieruOption{
Name: "test",
Server: "example.com",
Port: "10006-10007,11000",
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "example.com:10006",
wantConfig: &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String("test"),
User: &mierupb.User{
Name: proto.String("test"),
Password: proto.String("test"),
},
Servers: []*mierupb.ServerEndpoint{
{
DomainName: proto.String("example.com"),
PortBindings: []*mierupb.PortBinding{
{
PortRange: proto.String("10006-10007"),
Protocol: transportProtocol,
},
{
Port: proto.Int32(11000),
Protocol: transportProtocol,
},
},
},
},
},
},
},
{
option: MieruOption{
Name: "test",
Server: "example.com",
Port: "10008",
PortRange: "10009-10010",
Transport: "TCP",
UserName: "test",
Password: "test",
},
wantBaseAddr: "example.com:10008",
wantConfig: &mieruclient.ClientConfig{
Profile: &mierupb.ClientProfile{
ProfileName: proto.String("test"),
User: &mierupb.User{
Name: proto.String("test"),
Password: proto.String("test"),
},
Servers: []*mierupb.ServerEndpoint{
{
DomainName: proto.String("example.com"),
PortBindings: []*mierupb.PortBinding{
{
Port: proto.Int32(10008),
Protocol: transportProtocol,
},
{
PortRange: proto.String("10009-10010"),
Protocol: transportProtocol,
},
},
},
},
},
},
},
}
for _, testCase := range testCases {
mieru, err := NewMieru(testCase.option)
if err != nil {
t.Error(err)
t.Fatal(err)
}
config, err := mieru.client.Load()
if err != nil {
t.Fatal(err)
}
config.Dialer = nil
if mieru.addr != testCase.wantBaseAddr {
t.Errorf("got addr %q, want %q", mieru.addr, testCase.wantBaseAddr)
}
if !reflect.DeepEqual(config, testCase.wantConfig) {
t.Errorf("got config %+v, want %+v", config, testCase.wantConfig)
}
}
}
func TestNewMieruError(t *testing.T) {
testCases := []MieruOption{
{
Name: "test",
Server: "example.com",
Port: "invalid",
PortRange: "invalid",
Transport: "TCP",
UserName: "test",
Password: "test",
},
{
Name: "test",
Server: "example.com",
Port: "",
PortRange: "",
Transport: "TCP",
UserName: "test",
Password: "test",
},
}
for _, option := range testCases {
_, err := NewMieru(option)
if err == nil {
t.Errorf("expected error for option %+v, but got nil", option)
}
}
}
@@ -63,6 +276,7 @@ func TestBeginAndEndPortFromPortRange(t *testing.T) {
{"1-10", 1, 10, false},
{"1000-2000", 1000, 2000, false},
{"65535-65535", 65535, 65535, false},
{"2000-1000", 0, 0, true},
{"1", 0, 0, true},
{"1-", 0, 0, true},
{"-10", 0, 0, true},

View File

@@ -13,6 +13,7 @@ import (
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp"
gost "github.com/metacubex/mihomo/transport/gost-plugin"
"github.com/metacubex/mihomo/transport/kcptun"
"github.com/metacubex/mihomo/transport/restls"
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
@@ -36,6 +37,7 @@ type ShadowSocks struct {
gostOption *gost.Option
shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restls.Config
kcptunClient *kcptun.Client
}
type ShadowSocksOption struct {
@@ -106,6 +108,32 @@ type restlsOption struct {
RestlsScript string `obfs:"restls-script,omitempty"`
}
type kcpTunOption struct {
Key string `obfs:"key,omitempty"`
Crypt string `obfs:"crypt,omitempty"`
Mode string `obfs:"mode,omitempty"`
Conn int `obfs:"conn,omitempty"`
AutoExpire int `obfs:"autoexpire,omitempty"`
ScavengeTTL int `obfs:"scavengettl,omitempty"`
MTU int `obfs:"mtu,omitempty"`
SndWnd int `obfs:"sndwnd,omitempty"`
RcvWnd int `obfs:"rcvwnd,omitempty"`
DataShard int `obfs:"datashard,omitempty"`
ParityShard int `obfs:"parityshard,omitempty"`
DSCP int `obfs:"dscp,omitempty"`
NoComp bool `obfs:"nocomp,omitempty"`
AckNodelay bool `obfs:"acknodelay,omitempty"`
NoDelay int `obfs:"nodelay,omitempty"`
Interval int `obfs:"interval,omitempty"`
Resend int `obfs:"resend,omitempty"`
NoCongestion int `obfs:"nc,omitempty"`
SockBuf int `obfs:"sockbuf,omitempty"`
SmuxVer int `obfs:"smuxver,omitempty"`
SmuxBuf int `obfs:"smuxbuf,omitempty"`
StreamBuf int `obfs:"streambuf,omitempty"`
KeepAlive int `obfs:"keepalive,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
useEarly := false
@@ -174,7 +202,27 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
var c net.Conn
if ss.kcptunClient != nil {
c, err = ss.kcptunClient.OpenStream(ctx, func(ctx context.Context) (net.PacketConn, net.Addr, error) {
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
return nil, nil, err
}
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil {
return nil, nil, err
}
return pc, addr, nil
})
} else {
c, err = dialer.DialContext(ctx, "tcp", ss.addr)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
@@ -256,6 +304,13 @@ func (ss *ShadowSocks) SupportUOT() bool {
return ss.option.UDPOverTCP
}
func (ss *ShadowSocks) Close() error {
if ss.kcptunClient != nil {
return ss.kcptunClient.Close()
}
return nil
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
method, err := shadowsocks.CreateMethod(option.Cipher, shadowsocks.MethodOptions{
@@ -271,6 +326,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restls.Config
var kcptunClient *kcptun.Client
obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@@ -384,6 +440,39 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
} else if option.Plugin == kcptun.Mode {
obfsMode = kcptun.Mode
kcptunOpt := &kcpTunOption{}
if err := decoder.Decode(option.PluginOpts, kcptunOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize kcptun-plugin error: %w", addr, err)
}
kcptunClient = kcptun.NewClient(kcptun.Config{
Key: kcptunOpt.Key,
Crypt: kcptunOpt.Crypt,
Mode: kcptunOpt.Mode,
Conn: kcptunOpt.Conn,
AutoExpire: kcptunOpt.AutoExpire,
ScavengeTTL: kcptunOpt.ScavengeTTL,
MTU: kcptunOpt.MTU,
SndWnd: kcptunOpt.SndWnd,
RcvWnd: kcptunOpt.RcvWnd,
DataShard: kcptunOpt.DataShard,
ParityShard: kcptunOpt.ParityShard,
DSCP: kcptunOpt.DSCP,
NoComp: kcptunOpt.NoComp,
AckNodelay: kcptunOpt.AckNodelay,
NoDelay: kcptunOpt.NoDelay,
Interval: kcptunOpt.Interval,
Resend: kcptunOpt.Resend,
NoCongestion: kcptunOpt.NoCongestion,
SockBuf: kcptunOpt.SockBuf,
SmuxVer: kcptunOpt.SmuxVer,
SmuxBuf: kcptunOpt.SmuxBuf,
StreamBuf: kcptunOpt.StreamBuf,
KeepAlive: kcptunOpt.KeepAlive,
})
option.UDPOverTCP = true // must open uot
}
switch option.UDPOverTCPVersion {
case uot.Version, uot.LegacyVersion:
@@ -414,5 +503,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig,
kcptunClient: kcptunClient,
}, nil
}

View File

@@ -24,7 +24,6 @@ import (
"github.com/metacubex/randv2"
utls "github.com/metacubex/utls"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
)
@@ -107,13 +106,8 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
if err != nil {
return nil, err
}
var aeadCipher cipher.AEAD
if utls.AesgcmPreferred(hello.CipherSuites) {
aesBlock, _ := aes.NewCipher(authKey)
aeadCipher, _ = cipher.NewGCM(aesBlock)
} else {
aeadCipher, _ = chacha20poly1305.New(authKey)
}
aesBlock, _ := aes.NewCipher(authKey)
aeadCipher, _ := cipher.NewGCM(aesBlock)
aeadCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
copy(hello.Raw[39:], hello.SessionId)
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)

View File

@@ -534,6 +534,37 @@ proxies: # socks5
version-hint: "tls12"
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
- name: "ss-kcptun"
type: ss
server: [YOUR_SERVER_IP]
port: 443
cipher: chacha20-ietf-poly1305
password: [YOUR_SS_PASSWORD]
plugin: kcptun
plugin-opts:
key: it's a secrect # pre-shared secret between client and server
crypt: aes # aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none, null
mode: fast # profiles: fast3, fast2, fast, normal, manual
conn: 1 # set num of UDP connections to server
autoexpire: 0 # set auto expiration time(in seconds) for a single UDP connection, 0 to disable
scavengettl: 600 # set how long an expired connection can live (in seconds)
mtu: 1350 # set maximum transmission unit for UDP packets
sndwnd: 128 # set send window size(num of packets)
rcvwnd: 512 # set receive window size(num of packets)
datashard: 10 # set reed-solomon erasure coding - datashard
parityshard: 3 # set reed-solomon erasure coding - parityshard
dscp: 0 # set DSCP(6bit)
nocomp: false # disable compression
acknodelay: false # flush ack immediately when a packet is received
nodelay: 0
interval: 50
resend: false
sockbuf: 4194304 # per-socket buffer in bytes
smuxver: 1 # specify smux version, available 1,2
smuxbuf: 4194304 # the overall de-mux buffer in bytes
streambuf: 2097152 # per stream receive buffer in bytes, smux v2+
keepalive: 10 # seconds between heartbeats
# vmess
# cipher 支持 auto/aes-128-gcm/chacha20-poly1305/none
- name: "vmess"
@@ -993,8 +1024,8 @@ proxies: # socks5
- name: mieru
type: mieru
server: 1.2.3.4
port: 2999
# port-range: 2090-2099 #(不可同时填写 port 和 port-range
port: 2999 # 支持使用 ports 格式,例如 2999,3999 或 2999-3010,3950,3995-3999
# port-range: 2090-2099 # 已废弃,请使用 port
transport: TCP # 只支持 TCP
udp: true # 支持 UDP over TCP
username: user
@@ -1336,6 +1367,30 @@ listeners:
# password: password
# handshake:
# dest: test.com:443
# kcp-tun:
# enable: false
# key: it's a secrect # pre-shared secret between client and server
# crypt: aes # aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none, null
# mode: fast # profiles: fast3, fast2, fast, normal, manual
# conn: 1 # set num of UDP connections to server
# autoexpire: 0 # set auto expiration time(in seconds) for a single UDP connection, 0 to disable
# scavengettl: 600 # set how long an expired connection can live (in seconds)
# mtu: 1350 # set maximum transmission unit for UDP packets
# sndwnd: 128 # set send window size(num of packets)
# rcvwnd: 512 # set receive window size(num of packets)
# datashard: 10 # set reed-solomon erasure coding - datashard
# parityshard: 3 # set reed-solomon erasure coding - parityshard
# dscp: 0 # set DSCP(6bit)
# nocomp: false # disable compression
# acknodelay: false # flush ack immediately when a packet is received
# nodelay: 0
# interval: 50
# resend: false
# sockbuf: 4194304 # per-socket buffer in bytes
# smuxver: 1 # specify smux version, available 1,2
# smuxbuf: 4194304 # the overall de-mux buffer in bytes
# streambuf: 2097152 # per stream receive buffer in bytes, smux v2+
# keepalive: 10 # seconds between heartbeats
- name: vmess-in-1
type: vmess

View File

@@ -11,6 +11,7 @@ require (
github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.3.2
github.com/golang/snappy v1.0.0
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
github.com/mdlayher/netlink v1.7.2
@@ -21,6 +22,7 @@ require (
github.com/metacubex/chacha v0.1.5
github.com/metacubex/fswatch v0.1.1
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295
github.com/metacubex/randv2 v0.2.0
github.com/metacubex/restls-client-go v0.1.7
@@ -33,9 +35,9 @@ require (
github.com/metacubex/sing-tun v0.4.8
github.com/metacubex/sing-vmess v0.2.4
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
github.com/mroth/weightedrand/v2 v2.1.0
@@ -83,6 +85,8 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.3 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect

View File

@@ -60,6 +60,8 @@ github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -77,6 +79,10 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -106,6 +112,8 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b h1:z7JLKjugnQ1qvDOAD8yMA5C8AlJY3bG+VrrgRntRlUY=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
@@ -133,12 +141,12 @@ github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkA
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2 h1:5OGzQvoE5yuOe8AsZsFwhf32ZxKmKN9G+k06AVd+6jY=
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2/go.mod h1:GN/CB3TRwQ9LYquYpIFynDkvMTYmkjwI7+mkUIoHj88=
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e h1:t9IxEaxSRp3YJ1ewQV4oGkKaJaMeSoUWjOV0boLVQo8=
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -198,7 +206,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.6.1 h1:uVRTItFeNHkMcLueHS7OCsxgxT9P8MzGB/taUa2Y4Tk=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -216,6 +223,7 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
@@ -256,6 +264,7 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -0,0 +1,8 @@
package config
import "github.com/metacubex/mihomo/transport/kcptun"
type KcpTun struct {
Enable bool `json:"enable"`
kcptun.Config `json:",inline"`
}

View File

@@ -14,6 +14,7 @@ type ShadowsocksServer struct {
Udp bool
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
ShadowTLS ShadowTLS `yaml:"shadow-tls" json:"shadow-tls,omitempty"`
KcpTun KcpTun `yaml:"kcp-tun" json:"kcp-tun,omitempty"`
}
func (t ShadowsocksServer) String() string {

View File

@@ -0,0 +1,64 @@
package inbound
import (
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/transport/kcptun"
)
type KcpTun struct {
Enable bool `inbound:"enable"`
Key string `inbound:"key,omitempty"`
Crypt string `inbound:"crypt,omitempty"`
Mode string `inbound:"mode,omitempty"`
Conn int `inbound:"conn,omitempty"`
AutoExpire int `inbound:"autoexpire,omitempty"`
ScavengeTTL int `inbound:"scavengettl,omitempty"`
MTU int `inbound:"mtu,omitempty"`
SndWnd int `inbound:"sndwnd,omitempty"`
RcvWnd int `inbound:"rcvwnd,omitempty"`
DataShard int `inbound:"datashard,omitempty"`
ParityShard int `inbound:"parityshard,omitempty"`
DSCP int `inbound:"dscp,omitempty"`
NoComp bool `inbound:"nocomp,omitempty"`
AckNodelay bool `inbound:"acknodelay,omitempty"`
NoDelay int `inbound:"nodelay,omitempty"`
Interval int `inbound:"interval,omitempty"`
Resend int `inbound:"resend,omitempty"`
NoCongestion int `inbound:"nc,omitempty"`
SockBuf int `inbound:"sockbuf,omitempty"`
SmuxVer int `inbound:"smuxver,omitempty"`
SmuxBuf int `inbound:"smuxbuf,omitempty"`
StreamBuf int `inbound:"streambuf,omitempty"`
KeepAlive int `inbound:"keepalive,omitempty"`
}
func (c KcpTun) Build() LC.KcpTun {
return LC.KcpTun{
Enable: c.Enable,
Config: kcptun.Config{
Key: c.Key,
Crypt: c.Crypt,
Mode: c.Mode,
Conn: c.Conn,
AutoExpire: c.AutoExpire,
ScavengeTTL: c.ScavengeTTL,
MTU: c.MTU,
SndWnd: c.SndWnd,
RcvWnd: c.RcvWnd,
DataShard: c.DataShard,
ParityShard: c.ParityShard,
DSCP: c.DSCP,
NoComp: c.NoComp,
AckNodelay: c.AckNodelay,
NoDelay: c.NoDelay,
Interval: c.Interval,
Resend: c.Resend,
NoCongestion: c.NoCongestion,
SockBuf: c.SockBuf,
SmuxVer: c.SmuxVer,
SmuxBuf: c.SmuxBuf,
StreamBuf: c.StreamBuf,
KeepAlive: c.KeepAlive,
},
}
}

View File

@@ -16,6 +16,7 @@ type ShadowSocksOption struct {
UDP bool `inbound:"udp,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
ShadowTLS ShadowTLS `inbound:"shadow-tls,omitempty"`
KcpTun KcpTun `inbound:"kcp-tun,omitempty"`
}
func (o ShadowSocksOption) Equal(config C.InboundConfig) bool {
@@ -45,6 +46,7 @@ func NewShadowSocks(options *ShadowSocksOption) (*ShadowSocks, error) {
Udp: options.UDP,
MuxOption: options.MuxOption.Build(),
ShadowTLS: options.ShadowTLS.Build(),
KcpTun: options.KcpTun.Build(),
},
}, nil
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/listener/inbound"
"github.com/metacubex/mihomo/transport/kcptun"
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
shadowsocks "github.com/metacubex/sing-shadowsocks"
@@ -21,7 +22,7 @@ import (
var noneList = []string{shadowsocks.MethodNone}
var shadowsocksCipherLists = [][]string{noneList, shadowaead.List, shadowaead_2022.List, shadowstream.List}
var shadowsocksCipherShortLists = [][]string{noneList, shadowaead.List[:5]} // for test shadowTLS
var shadowsocksCipherShortLists = [][]string{noneList, shadowaead.List[:5]} // for test shadowTLS and kcptun
var shadowsocksPassword32 string
var shadowsocksPassword16 string
@@ -32,11 +33,11 @@ func init() {
shadowsocksPassword16 = base64.StdEncoding.EncodeToString(passwordBytes[:16])
}
func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption, cipherLists [][]string) {
func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption, cipherLists [][]string, enableSingMux bool) {
t.Parallel()
for _, cipherList := range cipherLists {
for i, cipher := range cipherList {
enableSingMux := i == 0
enableSingMux := enableSingMux && i == 0
cipher := cipher
t.Run(cipher, func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
@@ -100,19 +101,19 @@ func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOpt
func TestInboundShadowSocks_Basic(t *testing.T) {
inboundOptions := inbound.ShadowSocksOption{}
outboundOptions := outbound.ShadowSocksOption{}
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherLists)
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherLists, true)
}
func testInboundShadowSocksShadowTls(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) {
t.Parallel()
t.Run("Conn", func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists)
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, true)
})
t.Run("UConn", func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
outboundOptions.ClientFingerprint = "chrome"
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists)
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, true)
})
}
@@ -163,3 +164,17 @@ func TestInboundShadowSocks_ShadowTlsv3(t *testing.T) {
}
testInboundShadowSocksShadowTls(t, inboundOptions, outboundOptions)
}
func TestInboundShadowSocks_KcpTun(t *testing.T) {
inboundOptions := inbound.ShadowSocksOption{
KcpTun: inbound.KcpTun{
Enable: true,
Key: shadowsocksPassword16,
},
}
outboundOptions := outbound.ShadowSocksOption{
Plugin: kcptun.Mode,
PluginOpts: map[string]any{"key": shadowsocksPassword16},
}
testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists, false)
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/transport/kcptun"
shadowsocks "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowaead"
@@ -138,6 +139,12 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
}
}
var kcptunServer *kcptun.Server
if config.KcpTun.Enable {
kcptunServer = kcptun.NewServer(config.KcpTun.Config)
config.Udp = true
}
for _, addr := range strings.Split(config.Listen, ",") {
addr := addr
@@ -154,6 +161,14 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
sl.udpListeners = append(sl.udpListeners, ul)
if kcptunServer != nil {
go kcptunServer.Serve(ul, func(c net.Conn) {
sl.HandleConn(c, tunnel)
})
continue // skip tcp listener
}
go func() {
conn := bufio.NewPacketConn(ul)
rwOptions := network.NewReadWaitOptions(conn, sl.service)

View File

@@ -46,7 +46,7 @@ func NewClient(ctx context.Context, config ClientConfig) *Client {
}
// Initialize the padding state of this client
padding.UpdatePaddingScheme(padding.DefaultPaddingScheme, &c.padding)
c.sessionClient = session.NewClient(ctx, c.CreateOutboundTLSConnection, &c.padding, config.IdleSessionCheckInterval, config.IdleSessionTimeout, config.MinIdleSession)
c.sessionClient = session.NewClient(ctx, c.createOutboundTLSConnection, &c.padding, config.IdleSessionCheckInterval, config.IdleSessionTimeout, config.MinIdleSession)
return c
}
@@ -63,7 +63,7 @@ func (c *Client) CreateProxy(ctx context.Context, destination M.Socksaddr) (net.
return conn, nil
}
func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, error) {
func (c *Client) createOutboundTLSConnection(ctx context.Context) (net.Conn, error) {
conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.server)
if err != nil {
return nil, err

View File

@@ -66,23 +66,21 @@ func (c *Client) CreateStream(ctx context.Context) (net.Conn, error) {
var stream *Stream
var err error
for i := 0; i < 3; i++ {
session, err = c.findSession(ctx)
if session == nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
stream, err = session.OpenStream()
if err != nil {
_ = session.Close()
continue
}
break
session = c.getIdleSession()
if session == nil {
session, err = c.createSession(ctx)
}
if session == nil || stream == nil {
return nil, fmt.Errorf("too many closed session: %w", err)
if session == nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
stream, err = session.OpenStream()
if err != nil {
session.Close()
return nil, fmt.Errorf("failed to create stream: %w", err)
}
stream.dieHook = func() {
// If Session is not closed, put this Stream to pool
if !session.IsClosed() {
select {
case <-c.die.Done():
@@ -100,9 +98,7 @@ func (c *Client) CreateStream(ctx context.Context) (net.Conn, error) {
return stream, nil
}
func (c *Client) findSession(ctx context.Context) (*Session, error) {
var idle *Session
func (c *Client) getIdleSession() (idle *Session) {
c.idleSessionLock.Lock()
if !c.idleSession.IsEmpty() {
it := c.idleSession.Iterate()
@@ -110,12 +106,7 @@ func (c *Client) findSession(ctx context.Context) (*Session, error) {
c.idleSession.Remove(it.Key())
}
c.idleSessionLock.Unlock()
if idle == nil {
s, err := c.createSession(ctx)
return s, err
}
return idle, nil
return
}
func (c *Client) createSession(ctx context.Context) (*Session, error) {
@@ -127,7 +118,6 @@ func (c *Client) createSession(ctx context.Context) (*Session, error) {
session := NewClientSession(underlying, c.padding)
session.seq = c.sessionCounter.Add(1)
session.dieHook = func() {
//logrus.Debugln("session died", session)
c.idleSessionLock.Lock()
c.idleSession.Remove(math.MaxUint64 - session.seq)
c.idleSessionLock.Unlock()
@@ -168,12 +158,11 @@ func (c *Client) idleCleanup() {
}
func (c *Client) idleCleanupExpTime(expTime time.Time) {
sessionToRemove := make([]*Session, 0, c.idleSession.Len())
activeCount := 0
sessionToClose := make([]*Session, 0, c.idleSession.Len())
c.idleSessionLock.Lock()
it := c.idleSession.Iterate()
activeCount := 0
for it.IsNotEnd() {
session := it.Value()
key := it.Key()
@@ -190,12 +179,12 @@ func (c *Client) idleCleanupExpTime(expTime time.Time) {
continue
}
sessionToRemove = append(sessionToRemove, session)
sessionToClose = append(sessionToClose, session)
c.idleSession.Remove(key)
}
c.idleSessionLock.Unlock()
for _, session := range sessionToRemove {
for _, session := range sessionToClose {
session.Close()
}
}

View File

@@ -90,7 +90,7 @@ func (s *Session) Run() {
f := newFrame(cmdSettings, 0)
f.data = settings.ToBytes()
s.buffering = true
s.writeFrame(f)
s.writeControlFrame(f)
go s.recvLoop()
}
@@ -119,7 +119,7 @@ func (s *Session) Close() error {
}
s.streamLock.Lock()
for _, stream := range s.streams {
stream.Close()
stream.closeLocally()
}
s.streams = make(map[uint32]*Stream)
s.streamLock.Unlock()
@@ -138,8 +138,6 @@ func (s *Session) OpenStream() (*Stream, error) {
sid := s.streamId.Add(1)
stream := newStream(sid, s)
//logrus.Debugln("stream open", sid, s.streams)
if sid >= 2 && s.peerVersion >= 2 {
s.synDoneLock.Lock()
if s.synDone != nil {
@@ -151,7 +149,7 @@ func (s *Session) OpenStream() (*Stream, error) {
s.synDoneLock.Unlock()
}
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
if _, err := s.writeControlFrame(newFrame(cmdSYN, sid)); err != nil {
return nil, err
}
@@ -207,7 +205,7 @@ func (s *Session) recvLoop() error {
if !s.isClient && !receivedSettingsFromClient {
f := newFrame(cmdAlert, 0)
f.data = []byte("client did not send its settings")
s.writeFrame(f)
s.writeControlFrame(f)
return nil
}
s.streamLock.Lock()
@@ -241,18 +239,18 @@ func (s *Session) recvLoop() error {
stream, ok := s.streams[sid]
s.streamLock.RUnlock()
if ok {
stream.CloseWithError(fmt.Errorf("remote: %s", string(buffer)))
stream.closeWithError(fmt.Errorf("remote: %s", string(buffer)))
}
pool.Put(buffer)
}
case cmdFIN:
s.streamLock.RLock()
s.streamLock.Lock()
stream, ok := s.streams[sid]
s.streamLock.RUnlock()
delete(s.streams, sid)
s.streamLock.Unlock()
if ok {
stream.Close()
stream.closeLocally()
}
//logrus.Debugln("stream fin", sid, s.streams)
case cmdWaste:
if hdr.Length() > 0 {
buffer := pool.Get(int(hdr.Length()))
@@ -274,10 +272,9 @@ func (s *Session) recvLoop() error {
m := util.StringMapFromBytes(buffer)
paddingF := s.padding.Load()
if m["padding-md5"] != paddingF.Md5 {
// logrus.Debugln("remote md5 is", m["padding-md5"])
f := newFrame(cmdUpdatePaddingScheme, 0)
f.data = paddingF.RawScheme
_, err = s.writeFrame(f)
_, err = s.writeControlFrame(f)
if err != nil {
pool.Put(buffer)
return err
@@ -291,7 +288,7 @@ func (s *Session) recvLoop() error {
f.data = util.StringMap{
"v": "2",
}.ToBytes()
_, err = s.writeFrame(f)
_, err = s.writeControlFrame(f)
if err != nil {
pool.Put(buffer)
return err
@@ -329,7 +326,7 @@ func (s *Session) recvLoop() error {
}
}
case cmdHeartRequest:
if _, err := s.writeFrame(newFrame(cmdHeartResponse, sid)); err != nil {
if _, err := s.writeControlFrame(newFrame(cmdHeartResponse, sid)); err != nil {
return err
}
case cmdHeartResponse:
@@ -364,14 +361,31 @@ func (s *Session) streamClosed(sid uint32) error {
if s.IsClosed() {
return io.ErrClosedPipe
}
_, err := s.writeFrame(newFrame(cmdFIN, sid))
_, err := s.writeControlFrame(newFrame(cmdFIN, sid))
s.streamLock.Lock()
delete(s.streams, sid)
s.streamLock.Unlock()
return err
}
func (s *Session) writeFrame(frame frame) (int, error) {
func (s *Session) writeDataFrame(sid uint32, data []byte) (int, error) {
dataLen := len(data)
buffer := buf.NewSize(dataLen + headerOverHeadSize)
buffer.WriteByte(cmdPSH)
binary.BigEndian.PutUint32(buffer.Extend(4), sid)
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(dataLen))
buffer.Write(data)
_, err := s.writeConn(buffer.Bytes())
buffer.Release()
if err != nil {
return 0, err
}
return dataLen, nil
}
func (s *Session) writeControlFrame(frame frame) (int, error) {
dataLen := len(frame.data)
buffer := buf.NewSize(dataLen + headerOverHeadSize)
@@ -379,12 +393,18 @@ func (s *Session) writeFrame(frame frame) (int, error) {
binary.BigEndian.PutUint32(buffer.Extend(4), frame.sid)
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(dataLen))
buffer.Write(frame.data)
s.conn.SetWriteDeadline(time.Now().Add(time.Second * 5))
_, err := s.writeConn(buffer.Bytes())
buffer.Release()
if err != nil {
s.Close()
return 0, err
}
s.conn.SetWriteDeadline(time.Time{})
return dataLen, nil
}

View File

@@ -53,21 +53,35 @@ func (s *Stream) Write(b []byte) (n int, err error) {
return 0, os.ErrDeadlineExceeded
default:
}
f := newFrame(cmdPSH, s.id)
f.data = b
n, err = s.sess.writeFrame(f)
if s.dieErr != nil {
return 0, s.dieErr
}
n, err = s.sess.writeDataFrame(s.id, b)
return
}
// Close implements net.Conn
func (s *Stream) Close() error {
return s.CloseWithError(io.ErrClosedPipe)
return s.closeWithError(io.ErrClosedPipe)
}
func (s *Stream) CloseWithError(err error) error {
// if err != io.ErrClosedPipe {
// logrus.Debugln(err)
// }
// closeLocally only closes Stream and don't notify remote peer
func (s *Stream) closeLocally() {
var once bool
s.dieOnce.Do(func() {
s.dieErr = net.ErrClosed
s.pipeR.Close()
once = true
})
if once {
if s.dieHook != nil {
s.dieHook()
s.dieHook = nil
}
}
}
func (s *Stream) closeWithError(err error) error {
var once bool
s.dieOnce.Do(func() {
s.dieErr = err
@@ -128,7 +142,7 @@ func (s *Stream) HandshakeFailure(err error) error {
if once && err != nil && s.sess.peerVersion >= 2 {
f := newFrame(cmdSYNACK, s.id)
f.data = []byte(err.Error())
if _, err := s.sess.writeFrame(f); err != nil {
if _, err := s.sess.writeControlFrame(f); err != nil {
return err
}
}
@@ -142,7 +156,7 @@ func (s *Stream) HandshakeSuccess() error {
once = true
})
if once && s.sess.peerVersion >= 2 {
if _, err := s.sess.writeFrame(newFrame(cmdSYNACK, s.id)); err != nil {
if _, err := s.sess.writeControlFrame(newFrame(cmdSYNACK, s.id)); err != nil {
return err
}
}

View File

@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2016 xtaci
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,171 @@
package kcptun
import (
"context"
"net"
"sync"
"time"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/kcp-go"
"github.com/metacubex/randv2"
"github.com/metacubex/smux"
)
const Mode = "kcptun"
type DialFn func(ctx context.Context) (net.PacketConn, net.Addr, error)
type Client struct {
once sync.Once
config Config
block kcp.BlockCrypt
ctx context.Context
cancel context.CancelFunc
numconn uint16
muxes []timedSession
rr uint16
connMu sync.Mutex
chScavenger chan timedSession
}
func NewClient(config Config) *Client {
config.FillDefaults()
block := config.NewBlock()
ctx, cancel := context.WithCancel(context.Background())
return &Client{
config: config,
block: block,
ctx: ctx,
cancel: cancel,
}
}
func (c *Client) Close() error {
c.cancel()
return nil
}
func (c *Client) createConn(ctx context.Context, dial DialFn) (*smux.Session, error) {
conn, addr, err := dial(ctx)
if err != nil {
return nil, err
}
config := c.config
convid := randv2.Uint32()
kcpconn, err := kcp.NewConn4(convid, addr, c.block, config.DataShard, config.ParityShard, true, conn)
if err != nil {
return nil, err
}
kcpconn.SetStreamMode(true)
kcpconn.SetWriteDelay(false)
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
kcpconn.SetMtu(config.MTU)
kcpconn.SetACKNoDelay(config.AckNodelay)
_ = kcpconn.SetDSCP(config.DSCP)
_ = kcpconn.SetReadBuffer(config.SockBuf)
_ = kcpconn.SetWriteBuffer(config.SockBuf)
smuxConfig := smux.DefaultConfig()
smuxConfig.Version = config.SmuxVer
smuxConfig.MaxReceiveBuffer = config.SmuxBuf
smuxConfig.MaxStreamBuffer = config.StreamBuf
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
if smuxConfig.KeepAliveInterval >= smuxConfig.KeepAliveTimeout {
smuxConfig.KeepAliveTimeout = 3 * smuxConfig.KeepAliveInterval
}
if err := smux.VerifyConfig(smuxConfig); err != nil {
return nil, err
}
var netConn net.Conn = kcpconn
if !config.NoComp {
netConn = NewCompStream(netConn)
}
// stream multiplex
return smux.Client(netConn, smuxConfig)
}
func (c *Client) OpenStream(ctx context.Context, dial DialFn) (*smux.Stream, error) {
c.once.Do(func() {
// start scavenger if autoexpire is set
c.chScavenger = make(chan timedSession, 128)
if c.config.AutoExpire > 0 {
go scavenger(c.ctx, c.chScavenger, &c.config)
}
c.numconn = uint16(c.config.Conn)
c.muxes = make([]timedSession, c.config.Conn)
c.rr = uint16(0)
})
c.connMu.Lock()
idx := c.rr % c.numconn
// do auto expiration && reconnection
if c.muxes[idx].session == nil || c.muxes[idx].session.IsClosed() ||
(c.config.AutoExpire > 0 && time.Now().After(c.muxes[idx].expiryDate)) {
var err error
c.muxes[idx].session, err = c.createConn(ctx, dial)
if err != nil {
c.connMu.Unlock()
return nil, err
}
c.muxes[idx].expiryDate = time.Now().Add(time.Duration(c.config.AutoExpire) * time.Second)
if c.config.AutoExpire > 0 { // only when autoexpire set
c.chScavenger <- c.muxes[idx]
}
}
c.rr++
session := c.muxes[idx].session
c.connMu.Unlock()
return session.OpenStream()
}
// timedSession is a wrapper for smux.Session with expiry date
type timedSession struct {
session *smux.Session
expiryDate time.Time
}
// scavenger goroutine is used to close expired sessions
func scavenger(ctx context.Context, ch chan timedSession, config *Config) {
ticker := time.NewTicker(scavengePeriod * time.Second)
defer ticker.Stop()
var sessionList []timedSession
for {
select {
case item := <-ch:
sessionList = append(sessionList, timedSession{
item.session,
item.expiryDate.Add(time.Duration(config.ScavengeTTL) * time.Second)})
case <-ticker.C:
var newList []timedSession
for k := range sessionList {
s := sessionList[k]
if s.session.IsClosed() {
log.Debugln("scavenger: session normally closed: %s", s.session.LocalAddr())
} else if time.Now().After(s.expiryDate) {
s.session.Close()
log.Debugln("scavenger: session closed due to ttl: %s", s.session.LocalAddr())
} else {
newList = append(newList, sessionList[k])
}
}
sessionList = newList
case <-ctx.Done():
return
}
}
}

View File

@@ -0,0 +1,152 @@
package kcptun
import (
"crypto/sha1"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/kcp-go"
"golang.org/x/crypto/pbkdf2"
)
const (
// SALT is use for pbkdf2 key expansion
SALT = "kcp-go"
// maximum supported smux version
maxSmuxVer = 2
// scavenger check period
scavengePeriod = 5
)
type Config struct {
Key string `json:"key"`
Crypt string `json:"crypt"`
Mode string `json:"mode"`
Conn int `json:"conn"`
AutoExpire int `json:"autoexpire"`
ScavengeTTL int `json:"scavengettl"`
MTU int `json:"mtu"`
SndWnd int `json:"sndwnd"`
RcvWnd int `json:"rcvwnd"`
DataShard int `json:"datashard"`
ParityShard int `json:"parityshard"`
DSCP int `json:"dscp"`
NoComp bool `json:"nocomp"`
AckNodelay bool `json:"acknodelay"`
NoDelay int `json:"nodelay"`
Interval int `json:"interval"`
Resend int `json:"resend"`
NoCongestion int `json:"nc"`
SockBuf int `json:"sockbuf"`
SmuxVer int `json:"smuxver"`
SmuxBuf int `json:"smuxbuf"`
StreamBuf int `json:"streambuf"`
KeepAlive int `json:"keepalive"`
}
func (config *Config) FillDefaults() {
if config.Key == "" {
config.Key = "it's a secrect"
}
if config.Crypt == "" {
config.Crypt = "aes"
}
if config.Mode == "" {
config.Mode = "fast"
}
if config.Conn == 0 {
config.Conn = 1
}
if config.ScavengeTTL == 0 {
config.ScavengeTTL = 600
}
if config.MTU == 0 {
config.MTU = 1350
}
if config.SndWnd == 0 {
config.SndWnd = 128
}
if config.RcvWnd == 0 {
config.RcvWnd = 512
}
if config.DataShard == 0 {
config.DataShard = 10
}
if config.ParityShard == 0 {
config.ParityShard = 3
}
if config.Interval == 0 {
config.Interval = 50
}
if config.SockBuf == 0 {
config.SockBuf = 4194304
}
if config.SmuxVer == 0 {
config.SmuxVer = 1
}
if config.SmuxBuf == 0 {
config.SmuxBuf = 4194304
}
if config.StreamBuf == 0 {
config.StreamBuf = 2097152
}
if config.KeepAlive == 0 {
config.KeepAlive = 10
}
switch config.Mode {
case "normal":
config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 40, 2, 1
case "fast":
config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 0, 30, 2, 1
case "fast2":
config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 20, 2, 1
case "fast3":
config.NoDelay, config.Interval, config.Resend, config.NoCongestion = 1, 10, 2, 1
}
// SMUX Version check
if config.SmuxVer > maxSmuxVer {
log.Warnln("unsupported smux version: %d", config.SmuxVer)
config.SmuxVer = maxSmuxVer
}
// Scavenge parameters check
if config.AutoExpire != 0 && config.ScavengeTTL > config.AutoExpire {
log.Warnln("WARNING: scavengettl is bigger than autoexpire, connections may race hard to use bandwidth.")
log.Warnln("Try limiting scavengettl to a smaller value.")
}
}
func (config *Config) NewBlock() (block kcp.BlockCrypt) {
pass := pbkdf2.Key([]byte(config.Key), []byte(SALT), 4096, 32, sha1.New)
switch config.Crypt {
case "null":
block = nil
case "tea":
block, _ = kcp.NewTEABlockCrypt(pass[:16])
case "xor":
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
case "none":
block, _ = kcp.NewNoneBlockCrypt(pass)
case "aes-128":
block, _ = kcp.NewAESBlockCrypt(pass[:16])
case "aes-192":
block, _ = kcp.NewAESBlockCrypt(pass[:24])
case "blowfish":
block, _ = kcp.NewBlowfishBlockCrypt(pass)
case "twofish":
block, _ = kcp.NewTwofishBlockCrypt(pass)
case "cast5":
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
case "3des":
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
case "xtea":
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
case "salsa20":
block, _ = kcp.NewSalsa20BlockCrypt(pass)
default:
config.Crypt = "aes"
block, _ = kcp.NewAESBlockCrypt(pass)
}
return
}

View File

@@ -0,0 +1,63 @@
package kcptun
import (
"net"
"time"
"github.com/golang/snappy"
)
// CompStream is a net.Conn wrapper that compresses data using snappy
type CompStream struct {
conn net.Conn
w *snappy.Writer
r *snappy.Reader
}
func (c *CompStream) Read(p []byte) (n int, err error) {
return c.r.Read(p)
}
func (c *CompStream) Write(p []byte) (n int, err error) {
if _, err := c.w.Write(p); err != nil {
return 0, err
}
if err := c.w.Flush(); err != nil {
return 0, err
}
return len(p), err
}
func (c *CompStream) Close() error {
return c.conn.Close()
}
func (c *CompStream) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *CompStream) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *CompStream) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *CompStream) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *CompStream) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
// NewCompStream creates a new stream that compresses data using snappy
func NewCompStream(conn net.Conn) *CompStream {
c := new(CompStream)
c.conn = conn
c.w = snappy.NewBufferedWriter(conn)
c.r = snappy.NewReader(conn)
return c
}

View File

@@ -0,0 +1,5 @@
// Package kcptun copy and modify from:
// https://github.com/xtaci/kcptun/tree/52492c72592627d0005cbedbc4ba37fc36a95c3f
// adopt for mihomo
// without SM4,QPP,tcpraw support
package kcptun

View File

@@ -0,0 +1,79 @@
package kcptun
import (
"net"
"time"
"github.com/metacubex/kcp-go"
"github.com/metacubex/smux"
)
type Server struct {
config Config
block kcp.BlockCrypt
}
func NewServer(config Config) *Server {
config.FillDefaults()
block := config.NewBlock()
return &Server{
config: config,
block: block,
}
}
func (s *Server) Serve(pc net.PacketConn, handler func(net.Conn)) error {
lis, err := kcp.ServeConn(s.block, s.config.DataShard, s.config.ParityShard, pc)
if err != nil {
return err
}
defer lis.Close()
_ = lis.SetDSCP(s.config.DSCP)
_ = lis.SetReadBuffer(s.config.SockBuf)
_ = lis.SetWriteBuffer(s.config.SockBuf)
for {
conn, err := lis.AcceptKCP()
if err != nil {
return err
}
conn.SetStreamMode(true)
conn.SetWriteDelay(false)
conn.SetNoDelay(s.config.NoDelay, s.config.Interval, s.config.Resend, s.config.NoCongestion)
conn.SetMtu(s.config.MTU)
conn.SetWindowSize(s.config.SndWnd, s.config.RcvWnd)
conn.SetACKNoDelay(s.config.AckNodelay)
var netConn net.Conn = conn
if !s.config.NoComp {
netConn = NewCompStream(netConn)
}
go func() {
// stream multiplex
smuxConfig := smux.DefaultConfig()
smuxConfig.Version = s.config.SmuxVer
smuxConfig.MaxReceiveBuffer = s.config.SmuxBuf
smuxConfig.MaxStreamBuffer = s.config.StreamBuf
smuxConfig.KeepAliveInterval = time.Duration(s.config.KeepAlive) * time.Second
if smuxConfig.KeepAliveInterval >= smuxConfig.KeepAliveTimeout {
smuxConfig.KeepAliveTimeout = 3 * smuxConfig.KeepAliveInterval
}
mux, err := smux.Server(netConn, smuxConfig)
if err != nil {
return
}
defer mux.Close()
for {
stream, err := mux.AcceptStream()
if err != nil {
return
}
go handler(stream)
}
}()
}
}

View File

@@ -7,23 +7,12 @@ import (
"errors"
"io"
"net"
"runtime"
"sync"
"time"
"github.com/metacubex/blake3"
utls "github.com/metacubex/utls"
"github.com/metacubex/utls/mlkem"
"golang.org/x/sys/cpu"
)
var (
// Keep in sync with crypto/tls/cipher_suites.go.
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
)
type ClientInstance struct {
@@ -77,7 +66,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if i.NfsPKeys == nil {
return nil, errors.New("uninitialized")
}
c := NewCommonConn(conn, HasAESGCMHardwareSupport)
c := NewCommonConn(conn, utls.HasAESGCMHardwareSupport())
ivAndRealysLength := 16 + i.RelaysLength
pfsKeyExchangeLength := 18 + 1184 + 32 + 16

View File

@@ -0,0 +1,21 @@
package encryption
import (
"fmt"
"runtime"
"testing"
utls "github.com/metacubex/utls"
)
func TestHasAESGCMHardwareSupport(t *testing.T) {
fmt.Println("HasAESGCMHardwareSupport:", utls.HasAESGCMHardwareSupport())
if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" {
// It should be supported starting from Apple Silicon M1
// https://github.com/golang/go/blob/go1.25.0/src/internal/cpu/cpu_arm64_darwin.go#L26-L30
if !utls.HasAESGCMHardwareSupport() {
t.Errorf("For ARM64 Darwin platforms (excluding iOS), AES GCM hardware acceleration should always be available.")
}
}
}

View File

@@ -29,12 +29,15 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
@@ -48,6 +51,7 @@ require (
github.com/metacubex/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b // indirect
github.com/metacubex/mihomo v1.7.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
@@ -62,9 +66,9 @@ require (
github.com/metacubex/sing-tun v0.4.8 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2 // indirect
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect

View File

@@ -59,6 +59,8 @@ github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -76,6 +78,10 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -102,6 +108,8 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b h1:z7JLKjugnQ1qvDOAD8yMA5C8AlJY3bG+VrrgRntRlUY=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
@@ -129,12 +137,12 @@ github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkA
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2 h1:5OGzQvoE5yuOe8AsZsFwhf32ZxKmKN9G+k06AVd+6jY=
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2/go.mod h1:GN/CB3TRwQ9LYquYpIFynDkvMTYmkjwI7+mkUIoHj88=
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e h1:t9IxEaxSRp3YJ1ewQV4oGkKaJaMeSoUWjOV0boLVQo8=
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -192,7 +200,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/tiendc/go-deepcopy v1.6.1 h1:uVRTItFeNHkMcLueHS7OCsxgxT9P8MzGB/taUa2Y4Tk=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -210,6 +217,7 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
@@ -248,6 +256,7 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -36,12 +36,15 @@ require (
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
@@ -55,6 +58,7 @@ require (
github.com/metacubex/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
@@ -68,9 +72,9 @@ require (
github.com/metacubex/sing-tun v0.4.8 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2 // indirect
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect

View File

@@ -60,6 +60,8 @@ github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -77,6 +79,10 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -103,6 +109,8 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b h1:z7JLKjugnQ1qvDOAD8yMA5C8AlJY3bG+VrrgRntRlUY=
github.com/metacubex/kcp-go v0.0.0-20250923001605-1ba6f691c45b/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
@@ -130,12 +138,12 @@ github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkA
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=
github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2 h1:5OGzQvoE5yuOe8AsZsFwhf32ZxKmKN9G+k06AVd+6jY=
github.com/metacubex/utls v1.8.1-0.20250921102910-221428e5d4b2/go.mod h1:GN/CB3TRwQ9LYquYpIFynDkvMTYmkjwI7+mkUIoHj88=
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e h1:t9IxEaxSRp3YJ1ewQV4oGkKaJaMeSoUWjOV0boLVQo8=
github.com/metacubex/utls v1.8.1-0.20250923145048-0a5bbc90dd3e/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -193,7 +201,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/tiendc/go-deepcopy v1.6.1 h1:uVRTItFeNHkMcLueHS7OCsxgxT9P8MzGB/taUa2Y4Tk=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -211,6 +218,7 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
@@ -249,6 +257,7 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=