diff --git a/adapter/outbound/mieru.go b/adapter/outbound/mieru.go index bfdf0e51..8ef9cfd7 100644 --- a/adapter/outbound/mieru.go +++ b/adapter/outbound/mieru.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "strconv" - "strings" "sync" CN "github.com/metacubex/mihomo/common/net" @@ -31,8 +30,8 @@ type MieruOption struct { BasicOption Name string `proxy:"name"` Server string `proxy:"server"` - Port string `proxy:"port,omitempty"` - PortRange string `proxy:"port-range,omitempty"` // deprecated + Port int `proxy:"port,omitempty"` + PortRange string `proxy:"port-range,omitempty"` Transport string `proxy:"transport"` UDP bool `proxy:"udp,omitempty"` UserName string `proxy:"username"` @@ -124,19 +123,13 @@ 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 - var portStr string - if option.Port != "" { - portStr = option.Port + if option.Port != 0 { + addr = net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) } else { - portStr = option.PortRange + beginPort, _, _ := beginAndEndPortFromPortRange(option.PortRange) + addr = net.JoinHostPort(option.Server, strconv.Itoa(beginPort)) } - 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, @@ -190,62 +183,54 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro } transportProtocol := mierupb.TransportProtocol_TCP.Enum() - - 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 { - 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, + if option.PortRange != "" { + server = &mierupb.ServerEndpoint{ + IpAddress: proto.String(option.Server), + PortBindings: []*mierupb.PortBinding{ + { + PortRange: proto.String(option.PortRange), + 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 - server = &mierupb.ServerEndpoint{ - DomainName: proto.String(option.Server), - PortBindings: portBindings, + 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, + }, + }, + } } } - config := &mieruclient.ClientConfig{ Profile: &mierupb.ClientProfile{ ProfileName: proto.String(option.Name), @@ -274,9 +259,31 @@ func validateMieruOption(option MieruOption) error { if option.Server == "" { return fmt.Errorf("server is empty") } - if option.Port == "" && option.PortRange == "" { - return fmt.Errorf("port must be set") + if option.Port == 0 && option.PortRange == "" { + return fmt.Errorf("either port or port-range 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") } @@ -299,36 +306,8 @@ 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 } diff --git a/adapter/outbound/mieru_test.go b/adapter/outbound/mieru_test.go index 2b7976e4..086b7910 100644 --- a/adapter/outbound/mieru_test.go +++ b/adapter/outbound/mieru_test.go @@ -1,51 +1,22 @@ package outbound -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" -) +import "testing" 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{ @@ -57,212 +28,28 @@ 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.Fatal(err) + t.Error(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) - } } } @@ -276,7 +63,6 @@ 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}, diff --git a/docs/config.yaml b/docs/config.yaml index 090e2736..0650bc53 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -1024,8 +1024,8 @@ proxies: # socks5 - name: mieru type: mieru server: 1.2.3.4 - port: 2999 # 支持使用 ports 格式,例如 2999,3999 或 2999-3010,3950,3995-3999 - # port-range: 2090-2099 # 已废弃,请使用 port + port: 2999 + # port-range: 2090-2099 #(不可同时填写 port 和 port-range) transport: TCP # 只支持 TCP udp: true # 支持 UDP over TCP username: user