mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 08:20:05 +08:00
feat: support kcptun plugin for ss client/server
This commit is contained in:
parent
e28c8e6a51
commit
abe6c3bb35
@ -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
|
||||
}
|
||||
|
||||
@ -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"
|
||||
@ -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
|
||||
|
||||
4
go.mod
4
go.mod
@ -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-20250922034656-df9a2b90cdf7
|
||||
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
|
||||
@ -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
|
||||
|
||||
10
go.sum
10
go.sum
@ -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-20250922034656-df9a2b90cdf7 h1:vGsrjQxlepSfkMALzJuvDzd+wp6NvKXpoyPuPb4SYCE=
|
||||
github.com/metacubex/kcp-go v0.0.0-20250922034656-df9a2b90cdf7/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=
|
||||
@ -216,6 +224,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 +265,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=
|
||||
|
||||
8
listener/config/kcptun.go
Normal file
8
listener/config/kcptun.go
Normal 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"`
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
64
listener/inbound/kcptun.go
Normal file
64
listener/inbound/kcptun.go
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
9
transport/kcptun/LICENSE.md
Normal file
9
transport/kcptun/LICENSE.md
Normal 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.
|
||||
173
transport/kcptun/client.go
Normal file
173
transport/kcptun/client.go
Normal file
@ -0,0 +1,173 @@
|
||||
package kcptun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/metacubex/kcp-go"
|
||||
"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
|
||||
var convid uint32
|
||||
binary.Read(rand.Reader, binary.LittleEndian, &convid)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
152
transport/kcptun/common.go
Normal file
152
transport/kcptun/common.go
Normal 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
|
||||
}
|
||||
63
transport/kcptun/comp.go
Normal file
63
transport/kcptun/comp.go
Normal 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
|
||||
}
|
||||
5
transport/kcptun/doc.go
Normal file
5
transport/kcptun/doc.go
Normal 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
|
||||
79
transport/kcptun/server.go
Normal file
79
transport/kcptun/server.go
Normal 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)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user