chore: revert "chore: consolidate mieru port configuration (#2277)"
Some checks are pending
Test / test (1.20, macos-13) (push) Waiting to run
Test / test (1.20, macos-latest) (push) Waiting to run
Test / test (1.20, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.20, ubuntu-latest) (push) Waiting to run
Test / test (1.20, windows-latest) (push) Waiting to run
Test / test (1.21, macos-13) (push) Waiting to run
Test / test (1.21, macos-latest) (push) Waiting to run
Test / test (1.21, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.21, ubuntu-latest) (push) Waiting to run
Test / test (1.21, windows-latest) (push) Waiting to run
Test / test (1.22, macos-13) (push) Waiting to run
Test / test (1.22, macos-latest) (push) Waiting to run
Test / test (1.22, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.22, ubuntu-latest) (push) Waiting to run
Test / test (1.22, windows-latest) (push) Waiting to run
Test / test (1.23, macos-13) (push) Waiting to run
Test / test (1.23, macos-latest) (push) Waiting to run
Test / test (1.23, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.23, ubuntu-latest) (push) Waiting to run
Test / test (1.23, windows-latest) (push) Waiting to run
Test / test (1.24, macos-13) (push) Waiting to run
Test / test (1.24, macos-latest) (push) Waiting to run
Test / test (1.24, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.24, ubuntu-latest) (push) Waiting to run
Test / test (1.24, windows-latest) (push) Waiting to run
Test / test (1.25, macos-13) (push) Waiting to run
Test / test (1.25, macos-latest) (push) Waiting to run
Test / test (1.25, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.25, ubuntu-latest) (push) Waiting to run
Test / test (1.25, windows-latest) (push) Waiting to run
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run

The `port` field should not be allowed to be set to non-int values, as this would break some downstream assumptions that the option is an int.

This reverts commit 1b1f95aa9c.
This commit is contained in:
wwqgtxx 2025-09-28 20:25:40 +08:00
parent f45c6f5e91
commit f7bd8b83e5
3 changed files with 76 additions and 311 deletions

View File

@ -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
}

View File

@ -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},

View File

@ -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