From 72dbcd3ad4029e71360137a98480b1ef15e5c125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 18 Jul 2025 12:20:26 +0800 Subject: [PATCH] Improve darwin tun performance --- cmd/internal/tun_bench/main.go | 286 +++++++++++++++++++++++++++++++++ go.mod | 4 +- go.sum | 8 +- protocol/tun/inbound.go | 2 + 4 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 cmd/internal/tun_bench/main.go diff --git a/cmd/internal/tun_bench/main.go b/cmd/internal/tun_bench/main.go new file mode 100644 index 00000000..c1227a6b --- /dev/null +++ b/cmd/internal/tun_bench/main.go @@ -0,0 +1,286 @@ +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{ + //"/Users/sekai/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") + for _, flag := range flags { + sudoArgs = append(sudoArgs, flag) + } + } + 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(), "-t", "5"} + 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/go.mod b/go.mod index 790d9cd4..88bffc4f 100644 --- a/go.mod +++ b/go.mod @@ -28,13 +28,13 @@ require ( github.com/sagernet/gomobile v0.1.7 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.6.12-0.20250710134112-2f96887176ff + github.com/sagernet/sing v0.6.12-0.20250718132236-21daaa465e88 github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-quic v0.5.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.6.10-0.20250710134146-6aeee7bc7db4 + github.com/sagernet/sing-tun v0.6.10-0.20250718030019-3af7305b853e github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-mod.5 diff --git a/go.sum b/go.sum index 65a3334e..4ca50a46 100644 --- a/go.sum +++ b/go.sum @@ -168,8 +168,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.6.12-0.20250710134112-2f96887176ff h1:2x29M8i86PwGEGqvWI1zJWw2BiT6WRg97ELRABZo0Xc= -github.com/sagernet/sing v0.6.12-0.20250710134112-2f96887176ff/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.12-0.20250718132236-21daaa465e88 h1:Wu6hu+JsZ2gsFRJMqGzaZJ4ctGnmNrLGm9ckmotBFOs= +github.com/sagernet/sing v0.6.12-0.20250718132236-21daaa465e88/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric= @@ -180,8 +180,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.6.10-0.20250710134146-6aeee7bc7db4 h1:OCGg2SecHgMhs+juCLoYAujZrpwWCy0/f7wF6KEF4EU= -github.com/sagernet/sing-tun v0.6.10-0.20250710134146-6aeee7bc7db4/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-tun v0.6.10-0.20250718030019-3af7305b853e h1:IH6N3oTKs4bqXLoKP7uFfIAAuZHCNq6OCV4MlrGGLqs= +github.com/sagernet/sing-tun v0.6.10-0.20250718030019-3af7305b853e/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI= github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index b33a7807..e018b642 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -180,6 +180,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo outputMark = tun.DefaultAutoRedirectOutputMark } networkManager := service.FromContext[adapter.NetworkManager](ctx) + multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000)) inbound := &Inbound{ tag: tag, ctx: ctx, @@ -213,6 +214,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo IncludePackage: options.IncludePackage, ExcludePackage: options.ExcludePackage, InterfaceMonitor: networkManager.InterfaceMonitor(), + EXP_MultiPendingPackets: multiPendingPackets, }, udpTimeout: udpTimeout, stack: options.Stack,