diff --git a/cmd/internal/tun_bench/main.go b/cmd/internal/tun_bench/main.go deleted file mode 100644 index e62841dc9..000000000 --- a/cmd/internal/tun_bench/main.go +++ /dev/null @@ -1,284 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "net/netip" - "os" - "os/exec" - "strings" - "syscall" - "time" - - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/include" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/shell" -) - -var iperf3Path string - -func main() { - err := main0() - if err != nil { - log.Fatal(err) - } -} - -func main0() error { - err := shell.Exec("sudo", "ls").Run() - if err != nil { - return err - } - results, err := runTests() - if err != nil { - return err - } - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(results) -} - -func runTests() ([]TestResult, error) { - boxPaths := []string{ - os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"), - //"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box", - "./sing-box", - } - stacks := []string{ - "gvisor", - "system", - } - mtus := []int{ - 1500, - 4064, - // 16384, - // 32768, - // 49152, - 65535, - } - flagList := [][]string{ - {}, - } - var results []TestResult - for _, boxPath := range boxPaths { - for _, stack := range stacks { - for _, mtu := range mtus { - if strings.HasPrefix(boxPath, ".") { - for _, flags := range flagList { - result, err := testOnce(boxPath, stack, mtu, false, flags) - if err != nil { - return nil, err - } - results = append(results, *result) - } - } else { - result, err := testOnce(boxPath, stack, mtu, false, nil) - if err != nil { - return nil, err - } - results = append(results, *result) - } - } - } - } - return results, nil -} - -type TestResult struct { - BoxPath string `json:"box_path"` - Stack string `json:"stack"` - MTU int `json:"mtu"` - Flags []string `json:"flags"` - MultiThread bool `json:"multi_thread"` - UploadSpeed string `json:"upload_speed"` - DownloadSpeed string `json:"download_speed"` -} - -func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) { - testAddress := netip.MustParseAddr("1.1.1.1") - testConfig := option.Options{ - Inbounds: []option.Inbound{ - { - Type: C.TypeTun, - Options: &option.TunInboundOptions{ - Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")}, - AutoRoute: true, - MTU: uint32(mtu), - Stack: stackName, - RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())}, - }, - }, - }, - Route: &option.RouteOptions{ - Rules: []option.Rule{ - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - RawDefaultRule: option.RawDefaultRule{ - IPCIDR: []string{testAddress.String()}, - }, - RuleAction: option.RuleAction{ - Action: C.RuleActionTypeRouteOptions, - RouteOptionsOptions: option.RouteOptionsActionOptions{ - OverrideAddress: "127.0.0.1", - }, - }, - }, - }, - }, - AutoDetectInterface: true, - }, - } - ctx := include.Context(context.Background()) - tempConfig, err := os.CreateTemp("", "tun-bench-*.json") - if err != nil { - return - } - defer os.Remove(tempConfig.Name()) - encoder := json.NewEncoderContext(ctx, tempConfig) - encoder.SetIndent("", " ") - err = encoder.Encode(testConfig) - if err != nil { - return nil, E.Cause(err, "encode test config") - } - tempConfig.Close() - var sudoArgs []string - if len(flags) > 0 { - sudoArgs = append(sudoArgs, "env") - sudoArgs = append(sudoArgs, flags...) - } - sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name()) - boxProcess := shell.Exec("sudo", sudoArgs...) - boxProcess.Stdout = &stderrWriter{} - boxProcess.Stderr = io.Discard - err = boxProcess.Start() - if err != nil { - return - } - - if C.IsDarwin { - iperf3Path, err = exec.LookPath("iperf3-darwin") - } else { - iperf3Path, err = exec.LookPath("iperf3") - } - if err != nil { - return - } - serverProcess := shell.Exec(iperf3Path, "-s") - serverProcess.Stdout = io.Discard - serverProcess.Stderr = io.Discard - err = serverProcess.Start() - if err != nil { - return nil, E.Cause(err, "start iperf3 server") - } - - time.Sleep(time.Second) - - args := []string{"-c", testAddress.String()} - if multiThread { - args = append(args, "-P", "10") - } - - uploadProcess := shell.Exec(iperf3Path, args...) - output, err := uploadProcess.Read() - if err != nil { - boxProcess.Process.Signal(syscall.SIGKILL) - serverProcess.Process.Signal(syscall.SIGKILL) - println(output) - return - } - - uploadResult := common.SubstringBeforeLast(output, "iperf Done.") - uploadResult = common.SubstringBeforeLast(uploadResult, "sender") - uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec") - uploadResult = common.SubstringAfterLast(uploadResult, "Bytes") - uploadResult = strings.ReplaceAll(uploadResult, " ", "") - - result = &TestResult{ - BoxPath: boxPath, - Stack: stackName, - MTU: mtu, - Flags: flags, - MultiThread: multiThread, - UploadSpeed: uploadResult, - } - - downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...) - output, err = downloadProcess.Read() - if err != nil { - boxProcess.Process.Signal(syscall.SIGKILL) - serverProcess.Process.Signal(syscall.SIGKILL) - println(output) - return - } - - downloadResult := common.SubstringBeforeLast(output, "iperf Done.") - downloadResult = common.SubstringBeforeLast(downloadResult, "receiver") - downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec") - downloadResult = common.SubstringAfterLast(downloadResult, "Bytes") - downloadResult = strings.ReplaceAll(downloadResult, " ", "") - - result.DownloadSpeed = downloadResult - - printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult} - if len(flags) > 0 { - printArgs = append(printArgs, "flags", strings.Join(flags, " ")) - } - if multiThread { - printArgs = append(printArgs, "(-P 10)") - } - fmt.Println(printArgs...) - err = boxProcess.Process.Signal(syscall.SIGTERM) - if err != nil { - return - } - - err = serverProcess.Process.Signal(syscall.SIGTERM) - if err != nil { - return - } - - boxDone := make(chan struct{}) - go func() { - boxProcess.Cmd.Wait() - close(boxDone) - }() - - serverDone := make(chan struct{}) - go func() { - serverProcess.Process.Wait() - close(serverDone) - }() - - select { - case <-boxDone: - case <-time.After(2 * time.Second): - boxProcess.Process.Kill() - case <-time.After(4 * time.Second): - println("box process did not close!") - os.Exit(1) - } - - select { - case <-serverDone: - case <-time.After(2 * time.Second): - serverProcess.Process.Kill() - case <-time.After(4 * time.Second): - println("server process did not close!") - os.Exit(1) - } - - return -} - -type stderrWriter struct{} - -func (w *stderrWriter) Write(p []byte) (n int, err error) { - return os.Stderr.Write(p) -} diff --git a/cmd/sing-box/cmd_geoip_export.go b/cmd/sing-box/cmd_geoip_export.go index 6f59b4d53..414fc23fd 100644 --- a/cmd/sing-box/cmd_geoip_export.go +++ b/cmd/sing-box/cmd_geoip_export.go @@ -9,11 +9,14 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badoption" "github.com/oschwald/maxminddb-golang" "github.com/spf13/cobra" + "go4.org/netipx" ) var flagGeoipExportOutput string @@ -83,9 +86,13 @@ func geoipExport(countryCode string) error { encoder := json.NewEncoder(outputWriter) encoder.SetIndent("", " ") var headlessRule option.DefaultHeadlessRule - headlessRule.IPCIDR = make([]string, 0, len(ipNets)) + headlessRule.IPCIDR = make([]*badoption.Prefixable, 0, len(ipNets)) for _, cidr := range ipNets { - headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String()) + prefix, loaded := netipx.FromStdIPNet(cidr) + if !loaded { + return E.New("invalid network: ", cidr) + } + headlessRule.IPCIDR = append(headlessRule.IPCIDR, common.Ptr(badoption.Prefixable(prefix))) } var plainRuleSet option.PlainRuleSetCompat plainRuleSet.Version = C.RuleSetVersion2 diff --git a/common/srs/binary.go b/common/srs/binary.go index 6b48dc850..b15cadf35 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -206,7 +206,9 @@ func readDefaultRule(reader varbin.Reader, recover bool, mmap *mmapReader) (rule return } if recover { - rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String) + rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), func(it netip.Prefix) *badoption.Prefixable { + return common.Ptr(badoption.Prefixable(it)) + }) } case ruleItemIPCIDR: if mmap != nil { @@ -218,7 +220,9 @@ func readDefaultRule(reader varbin.Reader, recover bool, mmap *mmapReader) (rule return } if recover { - rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String) + rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), func(it netip.Prefix) *badoption.Prefixable { + return common.Ptr(badoption.Prefixable(it)) + }) } case ruleItemSourcePort: rule.SourcePort, err = varbin.ReadSlice[uint16](reader, binary.BigEndian) @@ -621,22 +625,12 @@ func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) e return binary.Write(writer, binary.BigEndian, value) } -func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string, rawSet *ipset.Set, mmap *mmapWriter) error { +func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []*badoption.Prefixable, rawSet *ipset.Set, mmap *mmapWriter) error { set := rawSet if len(value) > 0 { var builder netipx.IPSetBuilder - for i, prefixString := range value { - prefix, err := netip.ParsePrefix(prefixString) - if err == nil { - builder.AddPrefix(prefix) - continue - } - addr, addrErr := netip.ParseAddr(prefixString) - if addrErr == nil { - builder.Add(addr) - continue - } - return E.Cause(err, "parse [", i, "]") + for _, prefixable := range value { + builder.AddPrefix(prefixable.Build(netip.Prefix{})) } ipSet, err := builder.IPSet() if err != nil { diff --git a/common/srs/mmap_test.go b/common/srs/mmap_test.go index f18f6cd0c..04340ef6f 100644 --- a/common/srs/mmap_test.go +++ b/common/srs/mmap_test.go @@ -12,6 +12,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/json/badoption" "github.com/stretchr/testify/require" @@ -25,7 +26,11 @@ func TestMmapMatchesSource(t *testing.T) { DefaultOptions: option.DefaultHeadlessRule{ Domain: badoption.Listable[string]{"example.com"}, DomainSuffix: badoption.Listable[string]{".example.org"}, - IPCIDR: badoption.Listable[string]{"10.0.0.0/8", "2001:db8::/32", "192.0.2.1"}, + IPCIDR: badoption.Listable[*badoption.Prefixable]{ + common.Ptr(badoption.Prefixable(netip.MustParsePrefix("10.0.0.0/8"))), + common.Ptr(badoption.Prefixable(netip.MustParsePrefix("2001:db8::/32"))), + common.Ptr(badoption.Prefixable(netip.MustParsePrefix("192.0.2.1/32"))), + }, }, }, { @@ -36,7 +41,7 @@ func TestMmapMatchesSource(t *testing.T) { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultHeadlessRule{ AdGuardDomain: badoption.Listable[string]{"||ads.example.net^"}, - SourceIPCIDR: badoption.Listable[string]{"172.16.0.0/12"}, + SourceIPCIDR: badoption.Listable[*badoption.Prefixable]{common.Ptr(badoption.Prefixable(netip.MustParsePrefix("172.16.0.0/12")))}, }, }}, }, diff --git a/option/rule.go b/option/rule.go index c8369f368..a7e34c3fe 100644 --- a/option/rule.go +++ b/option/rule.go @@ -151,9 +151,9 @@ type RawDefaultRule struct { Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"` SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"` GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"` + SourceIPCIDR badoption.Listable[*badoption.Prefixable] `json:"source_ip_cidr,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"` + IPCIDR badoption.Listable[*badoption.Prefixable] `json:"ip_cidr,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 3a48b0ce7..ed7d982e1 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -147,7 +147,7 @@ type RawDefaultDNSRule struct { DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"` + SourceIPCIDR badoption.Listable[*badoption.Prefixable] `json:"source_ip_cidr,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` @@ -176,7 +176,7 @@ type RawDefaultDNSRule struct { RuleSet badoption.Listable[string] `json:"rule_set,omitempty" reference:"rule_set"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` MatchResponse *DNSRuleMatchResponse `json:"match_response,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"` + IPCIDR badoption.Listable[*badoption.Prefixable] `json:"ip_cidr,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"` IPAcceptAny bool `json:"ip_accept_any,omitempty"` ResponseRcode *DNSRCode `json:"response_rcode,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index 23b3cfe65..69837a8a1 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -254,8 +254,8 @@ type DefaultHeadlessRule struct { DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"` + SourceIPCIDR badoption.Listable[*badoption.Prefixable] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[*badoption.Prefixable] `json:"ip_cidr,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"` diff --git a/route/rule/rule_item_cidr.go b/route/rule/rule_item_cidr.go index 8c27994c2..dd47c1334 100644 --- a/route/rule/rule_item_cidr.go +++ b/route/rule/rule_item_cidr.go @@ -8,7 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/ipset" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" "go4.org/netipx" ) @@ -21,20 +21,10 @@ type IPCIDRItem struct { description string } -func NewIPCIDRItem(isSource bool, prefixStrings []string) (*IPCIDRItem, error) { +func NewIPCIDRItem(isSource bool, prefixables []*badoption.Prefixable) (*IPCIDRItem, error) { var builder netipx.IPSetBuilder - for i, prefixString := range prefixStrings { - prefix, err := netip.ParsePrefix(prefixString) - if err == nil { - builder.AddPrefix(prefix) - continue - } - addr, addrErr := netip.ParseAddr(prefixString) - if addrErr == nil { - builder.Add(addr) - continue - } - return nil, E.Cause(err, "parse [", i, "]") + for _, prefixable := range prefixables { + builder.AddPrefix(prefixable.Build(netip.Prefix{})) } var description string if isSource { @@ -42,12 +32,15 @@ func NewIPCIDRItem(isSource bool, prefixStrings []string) (*IPCIDRItem, error) { } else { description = "ip_cidr=" } - if dLen := len(prefixStrings); dLen == 1 { - description += prefixStrings[0] - } else if dLen > 3 { - description += "[" + strings.Join(prefixStrings[:3], " ") + "...]" + prefixString := func(it *badoption.Prefixable) string { + return it.Build(netip.Prefix{}).String() + } + if prefixCount := len(prefixables); prefixCount == 1 { + description += prefixString(prefixables[0]) + } else if prefixCount > 3 { + description += "[" + strings.Join(common.Map(prefixables[:3], prefixString), " ") + "...]" } else { - description += "[" + strings.Join(prefixStrings, " ") + "]" + description += "[" + strings.Join(common.Map(prefixables, prefixString), " ") + "]" } ipSet, err := builder.IPSet() if err != nil { diff --git a/route/rule/rule_set_semantics_test.go b/route/rule/rule_set_semantics_test.go index 0f63a0548..b6e2af0ab 100644 --- a/route/rule/rule_set_semantics_test.go +++ b/route/rule/rule_set_semantics_test.go @@ -10,6 +10,8 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/convertor/adguard" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" slogger "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -1432,7 +1434,7 @@ func addOtherItem(rule *abstractDefaultRule, item RuleItem) { func addSourceAddressItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) { t.Helper() - item, err := NewIPCIDRItem(true, cidrs) + item, err := NewIPCIDRItem(true, parsePrefixables(cidrs)) require.NoError(t, err) rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.allItems = append(rule.allItems, item) @@ -1462,7 +1464,7 @@ func addDestinationRegexItem(t *testing.T, rule *abstractDefaultRule, regexes [] func addDestinationIPCIDRItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) { t.Helper() - item, err := NewIPCIDRItem(false, cidrs) + item, err := NewIPCIDRItem(false, parsePrefixables(cidrs)) require.NoError(t, err) rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) @@ -1507,3 +1509,9 @@ func addDestinationPortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges rule.destinationPortItems = append(rule.destinationPortItems, item) rule.allItems = append(rule.allItems, item) } + +func parsePrefixables(cidrs []string) []*badoption.Prefixable { + return common.Map(cidrs, func(it string) *badoption.Prefixable { + return common.Ptr(badoption.Prefixable(netip.MustParsePrefix(it))) + }) +} diff --git a/schema/generator.go b/schema/generator.go index 1a7682585..f45f4a618 100644 --- a/schema/generator.go +++ b/schema/generator.go @@ -225,6 +225,9 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { if schemaTag == "omit" { continue } + if schemaTag != "" { + return E.New("unknown schema tag ", schemaTag, " on ", structType.Name(), ".", field.Name) + } if field.Anonymous && tagName == "" { err := g.FlattenStruct(node, fieldType) if err != nil { @@ -241,8 +244,8 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { g.path = append(g.path, structType.Name()+"."+tagName) var fieldNode *Node var err error - if enumTag != "" || examplesTag != "" || referenceTag != "" || schemaTag != "" { - fieldNode, err = g.taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag, schemaTag) + if enumTag != "" || examplesTag != "" || referenceTag != "" { + fieldNode, err = g.taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag) } else { fieldNode, err = g.Describe(fieldType) } @@ -255,7 +258,7 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { return nil } -func (g *generator) taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string, schemaTag string) (*Node, error) { +func (g *generator) taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string) (*Node, error) { elementType := fieldType for elementType.Kind() == reflect.Pointer { elementType = elementType.Elem() @@ -269,17 +272,9 @@ func (g *generator) taggedFieldNode(fieldType reflect.Type, enumTag string, exam } var element *Node var err error - switch { - case enumTag != "": + if enumTag != "" { element, err = taggedValueNode(elementType, strings.Split(enumTag, ",")) - case schemaTag == "prefixable": - if elementType.Kind() != reflect.String { - return nil, E.New("prefixable schema tags require a string field, got ", fieldType.String()) - } - element, err = g.Describe(prefixableType) - case schemaTag != "": - return nil, E.New("unknown schema tag ", schemaTag, " on ", fieldType.String()) - default: + } else { element, err = g.Describe(elementType) } if err != nil {