feat: support kcptun plugin for ss client/server

This commit is contained in:
wwqgtxx
2025-09-22 23:54:30 +08:00
parent e28c8e6a51
commit abe6c3bb35
16 changed files with 752 additions and 7 deletions

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)