diff --git a/extras/outbounds/acl/compile.go b/extras/outbounds/acl/compile.go index 40d0069..cd5c844 100644 --- a/extras/outbounds/acl/compile.go +++ b/extras/outbounds/acl/compile.go @@ -107,6 +107,11 @@ type GeoLoader interface { func Compile[O Outbound](rules []TextRule, outbounds map[string]O, cacheSize int, geoLoader GeoLoader, ) (CompiledRuleSet[O], error) { + for _, rule := range rules { + if extra, rangeLen := splitPortRangeRules(&rule); rangeLen > 0 { + rules = append(rules, extra...) + } + } compiledRules := make([]compiledRule[O], len(rules)) for i, rule := range rules { outbound, ok := outbounds[strings.ToLower(rule.Outbound)] @@ -282,3 +287,40 @@ func parseGeoSiteName(s string) (string, []string) { } return base, attrs } + +func splitPortRangeRules(rule *TextRule) ([]TextRule, int) { + protoPort := strings.ToLower(rule.ProtoPort) + if protoPort == "" || protoPort == "*" || protoPort == "*/*" { + return nil, 0 + } + parts := strings.SplitN(protoPort, "/", 2) + if len(parts) != 2 { + return nil, 0 + } + ports := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2) + if len(ports) != 2 { + return nil, 0 + } + minPorts, err := strconv.Atoi(ports[0]) + if err != nil { + return nil, 0 + } + maxPorts, err := strconv.Atoi(ports[1]) + if err != nil { + return nil, 0 + } + + portLength := maxPorts - minPorts + if portLength <= 0 || minPorts == 0 { + return nil, 0 + } + + // port range: minPort < port <= MaxPort + extraRules := make([]TextRule, portLength) + for i := range extraRules { + extraRules[i] = *rule + extraRules[i].ProtoPort = fmt.Sprintf("%s/%d", parts[0], minPorts+i+1) + } + rule.ProtoPort = fmt.Sprintf("%s/%d", parts[0], minPorts) + return extraRules, portLength +} diff --git a/extras/outbounds/acl/compile_test.go b/extras/outbounds/acl/compile_test.go index 772f8b6..87245b7 100644 --- a/extras/outbounds/acl/compile_test.go +++ b/extras/outbounds/acl/compile_test.go @@ -303,3 +303,76 @@ func Test_parseGeoSiteName(t *testing.T) { }) } } + +func Test_splitPortRangeRules(t *testing.T) { + rules := []TextRule{ + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "tcp/1-1024", + HijackAddress: "", + }, + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "udp/1-1024", + HijackAddress: "", + }, + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "*/1-1024", + HijackAddress: "", + }, + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "tcp/0-222", + HijackAddress: "", + }, + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "tcp/1024", + HijackAddress: "", + }, + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "tcp/-1-9", + HijackAddress: "", + }, + { + Outbound: "ob1", + Address: "1.2.3.4", + ProtoPort: "tcp/6881-6889", + HijackAddress: "", + }, + } + _, rangeLen0 := splitPortRangeRules(&rules[0]) + assert.Equal(t, 1023, rangeLen0) + + _, rangeLen1 := splitPortRangeRules(&rules[1]) + assert.Equal(t, 1023, rangeLen1) + + _, rangeLen2 := splitPortRangeRules(&rules[2]) + assert.Equal(t, 1023, rangeLen2) + + _, rangeLen3 := splitPortRangeRules(&rules[3]) + assert.Equal(t, 0, rangeLen3) + + _, rangeLen4 := splitPortRangeRules(&rules[4]) + assert.Equal(t, 0, rangeLen4) + + _, rangeLen5 := splitPortRangeRules(&rules[5]) + assert.Equal(t, 0, rangeLen5) + + rangeRule, _ := splitPortRangeRules(&rules[6]) + for _, rule := range rangeRule { + assert.Equal(t, "ob1", rule.Outbound) + assert.Equal(t, "1.2.3.4", rule.Address) + t.Log(rule.ProtoPort) + assert.Equal(t, "", rule.HijackAddress) + } + assert.Equal(t, "tcp/6881", rules[6].ProtoPort) +}