feat: support mieru traffic pattern configuration (#2585)

This commit is contained in:
enfein
2026-02-27 02:31:35 +00:00
committed by GitHub
parent f6722ab79b
commit c251e411e5
7 changed files with 85 additions and 23 deletions

View File

@@ -15,6 +15,7 @@ import (
mieruclient "github.com/enfein/mieru/v3/apis/client"
mierucommon "github.com/enfein/mieru/v3/apis/common"
mierumodel "github.com/enfein/mieru/v3/apis/model"
mierutp "github.com/enfein/mieru/v3/apis/trafficpattern"
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
"google.golang.org/protobuf/proto"
)
@@ -28,16 +29,17 @@ type Mieru struct {
type MieruOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
PortRange string `proxy:"port-range,omitempty"`
Transport string `proxy:"transport"`
UDP bool `proxy:"udp,omitempty"`
UserName string `proxy:"username"`
Password string `proxy:"password"`
Multiplexing string `proxy:"multiplexing,omitempty"`
HandshakeMode string `proxy:"handshake-mode,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
PortRange string `proxy:"port-range,omitempty"`
Transport string `proxy:"transport"`
UDP bool `proxy:"udp,omitempty"`
UserName string `proxy:"username"`
Password string `proxy:"password"`
Multiplexing string `proxy:"multiplexing,omitempty"`
HandshakeMode string `proxy:"handshake-mode,omitempty"`
TrafficPattern string `proxy:"traffic-pattern,omitempty"`
}
type mieruPacketDialer struct {
@@ -291,6 +293,10 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
if handshakeMode, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; ok {
config.Profile.HandshakeMode = (*mierupb.HandshakeMode)(&handshakeMode)
}
if option.TrafficPattern != "" {
trafficPattern, _ := mierutp.Decode(option.TrafficPattern)
config.Profile.TrafficPattern = trafficPattern
}
return config, nil
}
@@ -345,6 +351,15 @@ func validateMieruOption(option MieruOption) error {
return fmt.Errorf("invalid handshake mode: %s", option.HandshakeMode)
}
}
if option.TrafficPattern != "" {
trafficPattern, err := mierutp.Decode(option.TrafficPattern)
if err != nil {
return fmt.Errorf("failed to decode traffic pattern %q: %w", option.TrafficPattern, err)
}
if err := mierutp.Validate(trafficPattern); err != nil {
return fmt.Errorf("invalid traffic pattern %q: %w", option.TrafficPattern, err)
}
}
return nil
}

View File

@@ -31,12 +31,13 @@ func TestNewMieru(t *testing.T) {
},
{
option: MieruOption{
Name: "test",
Server: "example.com",
Port: 10003,
Transport: "UDP",
UserName: "test",
Password: "test",
Name: "test",
Server: "example.com",
Port: 10003,
Transport: "UDP",
UserName: "test",
Password: "test",
TrafficPattern: "GgQIARAK",
},
wantBaseAddr: "example.com:10003",
},

View File

@@ -1077,6 +1077,8 @@ proxies: # socks5
# multiplexing: MULTIPLEXING_LOW
# 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD
# handshake-mode: HANDSHAKE_STANDARD
# 一个 base64 字符串用于微调网络行为
# traffic-pattern: ""
# sudoku
- name: sudoku
@@ -1645,6 +1647,8 @@ listeners:
users:
username1: password1
username2: password2
# 一个 base64 字符串用于微调网络行为
# traffic-pattern: ""
- name: sudoku-in-1
type: sudoku

2
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/bahlo/generic-list-go v0.2.0
github.com/coreos/go-iptables v0.8.0
github.com/dlclark/regexp2 v1.11.5
github.com/enfein/mieru/v3 v3.26.2
github.com/enfein/mieru/v3 v3.28.0
github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.4.0
github.com/golang/snappy v1.0.0

4
go.sum
View File

@@ -20,8 +20,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/enfein/mieru/v3 v3.28.0 h1:4OsFPUIjKfQ6ymfyX1Laqz7h+zB8TxuK1m0isnYJ8ww=
github.com/enfein/mieru/v3 v3.28.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=

View File

@@ -14,6 +14,7 @@ import (
"google.golang.org/protobuf/proto"
mieruserver "github.com/enfein/mieru/v3/apis/server"
mierutp "github.com/enfein/mieru/v3/apis/trafficpattern"
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
)
@@ -26,8 +27,9 @@ type Mieru struct {
type MieruOption struct {
BaseOption
Transport string `inbound:"transport"`
Users map[string]string `inbound:"users"`
Transport string `inbound:"transport"`
Users map[string]string `inbound:"users"`
TrafficPattern string `inbound:"traffic-pattern"`
}
type mieruListenerFactory struct{}
@@ -154,10 +156,13 @@ func buildMieruServerConfig(option *MieruOption, ports utils.IntRanges[uint16])
Password: proto.String(password),
})
}
var trafficPattern *mierupb.TrafficPattern
trafficPattern, _ = mierutp.Decode(option.TrafficPattern)
return &mieruserver.ServerConfig{
Config: &mierupb.ServerConfig{
PortBindings: portBindings,
Users: users,
PortBindings: portBindings,
Users: users,
TrafficPattern: trafficPattern,
},
StreamListenerFactory: mieruListenerFactory{},
PacketListenerFactory: mieruListenerFactory{},
@@ -179,5 +184,14 @@ func validateMieruOption(option *MieruOption) error {
return fmt.Errorf("password is empty")
}
}
if option.TrafficPattern != "" {
trafficPattern, err := mierutp.Decode(option.TrafficPattern)
if err != nil {
return fmt.Errorf("failed to decode traffic pattern %q: %w", option.TrafficPattern, err)
}
if err := mierutp.Validate(trafficPattern); err != nil {
return fmt.Errorf("invalid traffic pattern %q: %w", option.TrafficPattern, err)
}
}
return nil
}

View File

@@ -61,6 +61,20 @@ func TestNewMieru(t *testing.T) {
},
wantErr: false,
},
{
name: "valid traffic pattern",
args: args{
option: &inbound.MieruOption{
BaseOption: inbound.BaseOption{
Port: "8080",
},
Transport: "TCP",
Users: map[string]string{"user": "pass"},
TrafficPattern: "GgQIARAK",
},
},
wantErr: false,
},
{
name: "invalid - no port",
args: args{
@@ -135,6 +149,20 @@ func TestNewMieru(t *testing.T) {
},
wantErr: true,
},
{
name: "invalid traffic pattern",
args: args{
option: &inbound.MieruOption{
BaseOption: inbound.BaseOption{
Port: "8080",
},
Transport: "TCP",
Users: map[string]string{"user": "pass"},
TrafficPattern: "1212ababXYYX",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {