diff --git a/adapter/outbound/mieru.go b/adapter/outbound/mieru.go index af790b6e..a70a042b 100644 --- a/adapter/outbound/mieru.go +++ b/adapter/outbound/mieru.go @@ -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 } diff --git a/adapter/outbound/mieru_test.go b/adapter/outbound/mieru_test.go index d80b2769..f47cf998 100644 --- a/adapter/outbound/mieru_test.go +++ b/adapter/outbound/mieru_test.go @@ -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", }, diff --git a/docs/config.yaml b/docs/config.yaml index 63e54dfe..864a9b80 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -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 diff --git a/go.mod b/go.mod index 8b777d0c..8fc052ba 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 724cc909..fb65418c 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/listener/inbound/mieru.go b/listener/inbound/mieru.go index 8a5718a6..5f7e4ffe 100644 --- a/listener/inbound/mieru.go +++ b/listener/inbound/mieru.go @@ -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 } diff --git a/listener/inbound/mieru_test.go b/listener/inbound/mieru_test.go index d57f4df5..af9e7826 100644 --- a/listener/inbound/mieru_test.go +++ b/listener/inbound/mieru_test.go @@ -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) {