From 6dd0aa78bf3f79da18bbb29b5c86c1001d75a60e Mon Sep 17 00:00:00 2001 From: Yusup Date: Sun, 14 Apr 2024 12:16:29 +0800 Subject: [PATCH 1/9] add mac routes --- .gitignore | 1 + cmd/swgp-go/main.go | 2 + cmd/swgp-go/main_darwin.go | 135 +++++++++++++++++++++++++++++++++++++ cmd/swgp-go/main_linux.go | 16 +++++ go.mod | 4 ++ go.sum | 16 +++++ 6 files changed, 174 insertions(+) create mode 100644 cmd/swgp-go/main_darwin.go create mode 100644 cmd/swgp-go/main_linux.go diff --git a/.gitignore b/.gitignore index ac3e9c3..fc0841c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /package-*/ /swgp-go /swgp-go.exe +.idea diff --git a/cmd/swgp-go/main.go b/cmd/swgp-go/main.go index 4f86727..6f082b3 100644 --- a/cmd/swgp-go/main.go +++ b/cmd/swgp-go/main.go @@ -86,9 +86,11 @@ func main() { sig := <-sigCh logger.Info("Received exit signal", slog.Any("signal", sig)) signal.Stop(sigCh) + cleanupHook(&sc, logger) cancel() }() + initHook(&sc, logger) if err = m.Start(ctx); err != nil { logger.Error("Failed to start services", tslog.Err(err)) os.Exit(1) diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go new file mode 100644 index 0000000..98dc8cb --- /dev/null +++ b/cmd/swgp-go/main_darwin.go @@ -0,0 +1,135 @@ +//go:build darwin + +package main + +import ( + "errors" + "github.com/database64128/swgp-go/service" + "go.uber.org/zap" + "golang.org/x/net/route" + "net" + "os/exec" + "syscall" + "time" +) + +func discoverGateway() (ip net.IP, err error) { + rib, err := route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0) + if err != nil { + return nil, err + } + + msgs, err := route.ParseRIB(syscall.NET_RT_DUMP, rib) + if err != nil { + return nil, err + } + + for _, m := range msgs { + switch m := m.(type) { + case *route.RouteMessage: + var ip net.IP + switch sa := m.Addrs[syscall.RTAX_GATEWAY].(type) { + case *route.Inet4Addr: + ip = net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) + return ip, nil + case *route.Inet6Addr: + ip = make(net.IP, net.IPv6len) + copy(ip, sa.IP[:]) + return ip, nil + } + } + } + return nil, errors.New("no ip found") +} + +func executeCommands(logger *zap.Logger, commands []string) error { + for _, cmdStr := range commands { + cmd := exec.Command("bash", "-c", cmdStr) + // Run the command and capture its output. + output, err := cmd.CombinedOutput() + if err != nil { + return err + } + logger.Info("Command executed", zap.String("output", string(output))) + } + return nil +} + +func addGatewayRoute(cfg *service.Config, logger *zap.Logger, gatewayIp net.IP, err error) { + for _, client := range cfg.Clients { + commands := []string{ + "sudo route delete " + client.ProxyEndpointAddress.IP().String(), + "sudo route add " + client.ProxyEndpointAddress.IP().String() + "/32 " + gatewayIp.String(), + } + err = executeCommands(logger, commands) + if err != nil { + logger.Error("Failed to recreate route:", zap.Error(err)) + } + } +} + +func deleteGatewayRoute(cfg *service.Config, logger *zap.Logger) { + for _, client := range cfg.Clients { + err := executeCommands(logger, []string{"sudo route delete " + client.ProxyEndpointAddress.IP().String()}) + if err != nil { + logger.Error("Failed to delete route:", zap.Error(err)) + } + } +} + +var macGateway = gatewayMonitor{} + +type gatewayMonitor struct { + ip net.IP + logger *zap.Logger + cfg *service.Config + cancelled chan struct{} +} + +func (g *gatewayMonitor) watch() { + for { + select { + case <-g.cancelled: + return + default: + ip, err := discoverGateway() + if err != nil { + g.logger.Error("Failed to get Gateway address:", zap.Error(err)) + } + if !g.ip.Equal(ip) { + g.logger.Info("Gateway address changed, reconfiguring routes") + deleteGatewayRoute(g.cfg, g.logger) + addGatewayRoute(g.cfg, g.logger, ip, err) + + // update ip + g.ip = ip + } + // sleep for 10 seconds + time.Sleep(10 * time.Second) + + // show current gateway route + g.logger.Info("Current gateway route:" + ip.String()) + } + } +} + +// add route +func initHook(cfg *service.Config, logger *zap.Logger) { + gatewayIp, err := discoverGateway() + if err != nil { + logger.Fatal("Failed to get Gateway address:", zap.Error(err)) + } + addGatewayRoute(cfg, logger, gatewayIp, err) + macGateway.ip = gatewayIp + macGateway.logger = logger + macGateway.cfg = cfg + macGateway.cancelled = make(chan struct{}) + go macGateway.watch() +} + +func cleanupHook(cfg *service.Config, logger *zap.Logger) { + deleteGatewayRoute(cfg, logger) + + // cancel gateway monitor + close(macGateway.cancelled) +} diff --git a/cmd/swgp-go/main_linux.go b/cmd/swgp-go/main_linux.go new file mode 100644 index 0000000..acaf1b9 --- /dev/null +++ b/cmd/swgp-go/main_linux.go @@ -0,0 +1,16 @@ +//go:build linux + +package main + +import ( + "github.com/database64128/swgp-go/service" + "go.uber.org/zap" +) + +func initHook(config service.Config, logger *zap.Logger) { + // NOOP +} + +func cleanerHook(config service.Config, logger *zap.Logger) { + // NOOP +} diff --git a/go.mod b/go.mod index 5d75f6f..cabe343 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,10 @@ go 1.23.0 require ( github.com/database64128/netx-go v0.0.0-20241205055133-3d4b4d263f10 github.com/lmittmann/tint v1.0.6 + go.uber.org/zap v1.27.0 golang.org/x/crypto v0.31.0 + golang.org/x/net v0.21.0 golang.org/x/sys v0.28.0 ) + +require go.uber.org/multierr v1.10.0 // indirect diff --git a/go.sum b/go.sum index c720db7..ce53533 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,24 @@ github.com/database64128/netx-go v0.0.0-20241205055133-3d4b4d263f10 h1:UJId3liaDh+tlJ1e3OmXqIevs9JFYXXo1K30Yx/nkrc= github.com/database64128/netx-go v0.0.0-20241205055133-3d4b4d263f10/go.mod h1:dqHsLB0Fb36Z2NSrzKklBf27+hLifGwPEGcGGXib3Rw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc= github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 05c0a0c88a6cca0feca8eb6dca2e8ae365b21d1b Mon Sep 17 00:00:00 2001 From: Yusup Date: Sun, 14 Apr 2024 14:47:05 +0800 Subject: [PATCH 2/9] refactor --- cmd/swgp-go/main_darwin.go | 60 +++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go index 98dc8cb..a4c6297 100644 --- a/cmd/swgp-go/main_darwin.go +++ b/cmd/swgp-go/main_darwin.go @@ -4,6 +4,7 @@ package main import ( "errors" + "fmt" "github.com/database64128/swgp-go/service" "go.uber.org/zap" "golang.org/x/net/route" @@ -13,33 +14,31 @@ import ( "time" ) -func discoverGateway() (ip net.IP, err error) { +func discoverGateway() (net.IP, error) { rib, err := route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to fetch RIB: %v", err) } msgs, err := route.ParseRIB(syscall.NET_RT_DUMP, rib) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse RIB: %v", err) } for _, m := range msgs { - switch m := m.(type) { - case *route.RouteMessage: - var ip net.IP - switch sa := m.Addrs[syscall.RTAX_GATEWAY].(type) { + if rm, ok := m.(*route.RouteMessage); ok { + addr := rm.Addrs[syscall.RTAX_GATEWAY] + switch sa := addr.(type) { case *route.Inet4Addr: - ip = net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) - return ip, nil + return net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]), nil case *route.Inet6Addr: - ip = make(net.IP, net.IPv6len) + ip := make(net.IP, net.IPv6len) copy(ip, sa.IP[:]) return ip, nil } } } - return nil, errors.New("no ip found") + return nil, errors.New("no default gateway found") } func executeCommands(logger *zap.Logger, commands []string) error { @@ -55,17 +54,29 @@ func executeCommands(logger *zap.Logger, commands []string) error { return nil } -func addGatewayRoute(cfg *service.Config, logger *zap.Logger, gatewayIp net.IP, err error) { +func addGatewayRoute(cfg *service.Config, logger *zap.Logger, gatewayIP net.IP) error { + logger.Info("Current gateway route:" + gatewayIP.String()) for _, client := range cfg.Clients { - commands := []string{ - "sudo route delete " + client.ProxyEndpointAddress.IP().String(), - "sudo route add " + client.ProxyEndpointAddress.IP().String() + "/32 " + gatewayIp.String(), + var commands []string + if gatewayIP.To4() != nil { + // IPv4 gateway + commands = []string{ + "sudo route delete " + client.ProxyEndpointAddress.IP().String(), + "sudo route add " + client.ProxyEndpointAddress.IP().String() + "/32 " + gatewayIP.String(), + } + } else { + // IPv6 gateway + commands = []string{ + "sudo route delete -inet6 " + client.ProxyEndpointAddress.IP().String(), + "sudo route add -inet6 " + client.ProxyEndpointAddress.IP().String() + "/128 " + gatewayIP.String(), + } } - err = executeCommands(logger, commands) + err := executeCommands(logger, commands) if err != nil { - logger.Error("Failed to recreate route:", zap.Error(err)) + return fmt.Errorf("failed to recreate route for client %s: %v", client.ProxyEndpointAddress.IP().String(), err) } } + return nil } func deleteGatewayRoute(cfg *service.Config, logger *zap.Logger) { @@ -99,16 +110,16 @@ func (g *gatewayMonitor) watch() { if !g.ip.Equal(ip) { g.logger.Info("Gateway address changed, reconfiguring routes") deleteGatewayRoute(g.cfg, g.logger) - addGatewayRoute(g.cfg, g.logger, ip, err) + err = addGatewayRoute(g.cfg, g.logger, ip) + if err != nil { + g.logger.Error("Failed to reconfigure routes:", zap.Error(err)) + } // update ip g.ip = ip } // sleep for 10 seconds time.Sleep(10 * time.Second) - - // show current gateway route - g.logger.Info("Current gateway route:" + ip.String()) } } } @@ -119,12 +130,15 @@ func initHook(cfg *service.Config, logger *zap.Logger) { if err != nil { logger.Fatal("Failed to get Gateway address:", zap.Error(err)) } - addGatewayRoute(cfg, logger, gatewayIp, err) + err = addGatewayRoute(cfg, logger, gatewayIp) + if err != nil { + logger.Fatal("Falied to add gateway route:", zap.Error(err)) + } macGateway.ip = gatewayIp macGateway.logger = logger macGateway.cfg = cfg macGateway.cancelled = make(chan struct{}) - go macGateway.watch() + //go macGateway.watch() } func cleanupHook(cfg *service.Config, logger *zap.Logger) { From 12e5081ca3ac024728ffc387e5e956db2716de20 Mon Sep 17 00:00:00 2001 From: Yusup Date: Sun, 14 Apr 2024 15:06:52 +0800 Subject: [PATCH 3/9] fix gateway --- cmd/swgp-go/main_darwin.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go index a4c6297..be8e69a 100644 --- a/cmd/swgp-go/main_darwin.go +++ b/cmd/swgp-go/main_darwin.go @@ -25,19 +25,26 @@ func discoverGateway() (net.IP, error) { return nil, fmt.Errorf("failed to parse RIB: %v", err) } + var ips []net.IP + for _, m := range msgs { if rm, ok := m.(*route.RouteMessage); ok { - addr := rm.Addrs[syscall.RTAX_GATEWAY] - switch sa := addr.(type) { - case *route.Inet4Addr: - return net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]), nil - case *route.Inet6Addr: - ip := make(net.IP, net.IPv6len) - copy(ip, sa.IP[:]) - return ip, nil + if rm.Flags&syscall.RTF_GATEWAY != 0 && rm.Flags&syscall.RTF_UP != 0 { + addr := rm.Addrs[syscall.RTAX_GATEWAY] + switch sa := addr.(type) { + case *route.Inet4Addr: + ips = append(ips, net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3])) + case *route.Inet6Addr: + ip := make(net.IP, net.IPv6len) + copy(ip, sa.IP[:]) + ips = append(ips, ip) + } } } } + if len(ips) > 0 { + return ips[0], nil + } return nil, errors.New("no default gateway found") } @@ -118,6 +125,7 @@ func (g *gatewayMonitor) watch() { // update ip g.ip = ip } + // sleep for 10 seconds time.Sleep(10 * time.Second) } @@ -138,7 +146,7 @@ func initHook(cfg *service.Config, logger *zap.Logger) { macGateway.logger = logger macGateway.cfg = cfg macGateway.cancelled = make(chan struct{}) - //go macGateway.watch() + go macGateway.watch() } func cleanupHook(cfg *service.Config, logger *zap.Logger) { From 5712d4a8b398e4eb2da945e173e8023b5803c214 Mon Sep 17 00:00:00 2001 From: Yusup Date: Sun, 14 Apr 2024 21:39:35 +0800 Subject: [PATCH 4/9] fix gateway --- cmd/swgp-go/main_darwin.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go index be8e69a..1484561 100644 --- a/cmd/swgp-go/main_darwin.go +++ b/cmd/swgp-go/main_darwin.go @@ -68,14 +68,14 @@ func addGatewayRoute(cfg *service.Config, logger *zap.Logger, gatewayIP net.IP) if gatewayIP.To4() != nil { // IPv4 gateway commands = []string{ - "sudo route delete " + client.ProxyEndpointAddress.IP().String(), - "sudo route add " + client.ProxyEndpointAddress.IP().String() + "/32 " + gatewayIP.String(), + "sudo route -n delete -net " + client.ProxyEndpointAddress.IP().String(), + "sudo route -n add -net " + client.ProxyEndpointAddress.IP().String() + "/32 -gateway " + gatewayIP.String(), } } else { // IPv6 gateway commands = []string{ - "sudo route delete -inet6 " + client.ProxyEndpointAddress.IP().String(), - "sudo route add -inet6 " + client.ProxyEndpointAddress.IP().String() + "/128 " + gatewayIP.String(), + "sudo route -n delete -inet6 -net " + client.ProxyEndpointAddress.IP().String(), + "sudo route -n add -inet6 -net " + client.ProxyEndpointAddress.IP().String() + "/128 -gateway " + gatewayIP.String(), } } err := executeCommands(logger, commands) @@ -88,7 +88,7 @@ func addGatewayRoute(cfg *service.Config, logger *zap.Logger, gatewayIP net.IP) func deleteGatewayRoute(cfg *service.Config, logger *zap.Logger) { for _, client := range cfg.Clients { - err := executeCommands(logger, []string{"sudo route delete " + client.ProxyEndpointAddress.IP().String()}) + err := executeCommands(logger, []string{"sudo route -n delete -net " + client.ProxyEndpointAddress.IP().String()}) if err != nil { logger.Error("Failed to delete route:", zap.Error(err)) } @@ -116,7 +116,6 @@ func (g *gatewayMonitor) watch() { } if !g.ip.Equal(ip) { g.logger.Info("Gateway address changed, reconfiguring routes") - deleteGatewayRoute(g.cfg, g.logger) err = addGatewayRoute(g.cfg, g.logger, ip) if err != nil { g.logger.Error("Failed to reconfigure routes:", zap.Error(err)) From ae68bb43b69d470e131f2502f318ce1fcba9b397 Mon Sep 17 00:00:00 2001 From: Yusup Date: Thu, 19 Dec 2024 02:39:17 +0800 Subject: [PATCH 5/9] refactor --- cmd/swgp-go/main.go | 2 +- cmd/swgp-go/main_darwin.go | 595 ++++++++++++++++++++++++++++++++----- cmd/swgp-go/main_linux.go | 2 +- go.mod | 2 +- go.sum | 4 +- 5 files changed, 528 insertions(+), 77 deletions(-) diff --git a/cmd/swgp-go/main.go b/cmd/swgp-go/main.go index 6f082b3..f431708 100644 --- a/cmd/swgp-go/main.go +++ b/cmd/swgp-go/main.go @@ -86,7 +86,7 @@ func main() { sig := <-sigCh logger.Info("Received exit signal", slog.Any("signal", sig)) signal.Stop(sigCh) - cleanupHook(&sc, logger) + cleanupHook() cancel() }() diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go index 1484561..d6367d8 100644 --- a/cmd/swgp-go/main_darwin.go +++ b/cmd/swgp-go/main_darwin.go @@ -3,18 +3,90 @@ package main import ( + "context" "errors" "fmt" - "github.com/database64128/swgp-go/service" - "go.uber.org/zap" - "golang.org/x/net/route" "net" - "os/exec" + "os" + "strings" + "sync" "syscall" "time" + "unsafe" + + "github.com/database64128/swgp-go/service" + "go.uber.org/zap" + "golang.org/x/net/route" +) + +var ( + ErrInvalidGateway = errors.New("invalid gateway address") + rtmError uint8 = 0x5 // RTM_ERROR, not exposed in syscall package ) -func discoverGateway() (net.IP, error) { +// GatewayMonitor handles gateway route monitoring and management +type GatewayMonitor struct { + mu sync.RWMutex + ip net.IP + logger *zap.Logger + cfg *service.Config + ctx context.Context + cancel context.CancelFunc + interval time.Duration + routeSocket int + seq int32 +} + +// NewGatewayMonitor creates a new gateway monitor instance +func NewGatewayMonitor(cfg *service.Config, logger *zap.Logger, interval time.Duration) (*GatewayMonitor, error) { + ctx, cancel := context.WithCancel(context.Background()) + + // Open routing socket + sock, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC) + if err != nil { + cancel() + return nil, fmt.Errorf("open route socket: %w", err) + } + + // Set socket options + err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_USELOOPBACK, 1) + if err != nil { + syscall.Close(sock) + cancel() + return nil, fmt.Errorf("set socket options: %w", err) + } + + // Discover initial gateway IP + monitor := &GatewayMonitor{ + cfg: cfg, + logger: logger, + interval: interval, + ctx: ctx, + cancel: cancel, + routeSocket: sock, + seq: 1, + } + + initialIP, err := monitor.discoverGateway() + if err != nil { + cancel() + syscall.Close(sock) + return nil, fmt.Errorf("discover initial gateway: %w", err) + } + monitor.ip = initialIP + + return monitor, nil +} + +// roundup rounds up length to the nearest multiple of 4 +func roundup(length int) int { + if length == 0 { + return 0 + } + return ((length) + 3) &^ 3 +} + +func (g *GatewayMonitor) discoverGateway() (net.IP, error) { rib, err := route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0) if err != nil { return nil, fmt.Errorf("failed to fetch RIB: %v", err) @@ -33,124 +105,503 @@ func discoverGateway() (net.IP, error) { addr := rm.Addrs[syscall.RTAX_GATEWAY] switch sa := addr.(type) { case *route.Inet4Addr: - ips = append(ips, net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3])) + ip := net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) + if isValidGateway(ip, g.logger) { + ips = append(ips, ip) + } case *route.Inet6Addr: ip := make(net.IP, net.IPv6len) copy(ip, sa.IP[:]) - ips = append(ips, ip) + if isValidGateway(ip, g.logger) { + ips = append(ips, ip) + } } } } } if len(ips) > 0 { + g.logger.Info("Found gateway", zap.String("ip", ips[0].String())) return ips[0], nil } - return nil, errors.New("no default gateway found") + return nil, fmt.Errorf("no default gateway found") } -func executeCommands(logger *zap.Logger, commands []string) error { - for _, cmdStr := range commands { - cmd := exec.Command("bash", "-c", cmdStr) - // Run the command and capture its output. - output, err := cmd.CombinedOutput() +func isValidGateway(ip net.IP, logger *zap.Logger) bool { + if ip == nil || ip.Equal(net.IPv4zero) { + logger.Debug("Invalid gateway: nil or zero IP") + return false + } + + // Check if it's a private network address (RFC 1918) + privateNetworks := []string{ + "10.0.0.0/8", // Class A + "172.16.0.0/12", // Class B + "192.168.0.0/16", // Class C + "169.254.0.0/16", // Link-local + } + + for _, network := range privateNetworks { + _, ipnet, err := net.ParseCIDR(network) if err != nil { - return err + logger.Error("Failed to parse CIDR", zap.String("network", network), zap.Error(err)) + continue + } + if ipnet.Contains(ip) { + return true + } + } + + logger.Debug("Invalid gateway: not in private or link-local range", zap.String("ip", ip.String())) + return false +} + +func (g *GatewayMonitor) addRoute(dest net.IP, gateway net.IP, prefixLen int) error { + if gateway.Equal(net.IPv4zero) { + return fmt.Errorf("invalid gateway address: %v", gateway) + } + + // Create routing message + rtmsg := &syscall.RtMsghdr{ + Type: syscall.RTM_ADD, + Version: syscall.RTM_VERSION, + Seq: g.seq, + Addrs: syscall.RTA_DST | syscall.RTA_GATEWAY | syscall.RTA_NETMASK, + Pid: 0, // Let kernel assign PID + Flags: syscall.RTF_UP | syscall.RTF_GATEWAY | syscall.RTF_STATIC, + } + g.seq++ + + // Calculate total message size + msgLen := syscall.SizeofRtMsghdr + syscall.SizeofSockaddrInet4*3 // Header + Dest + Gateway + Netmask + + // Create the message buffer + wb := make([]byte, msgLen) + + // Copy header + rtmsg.Msglen = uint16(msgLen) + *(*syscall.RtMsghdr)(unsafe.Pointer(&wb[0])) = *rtmsg + + // Add destination sockaddr + destAddr := syscall.RawSockaddrInet4{ + Len: syscall.SizeofSockaddrInet4, + Family: syscall.AF_INET, + } + copy(destAddr.Addr[:], dest.To4()) + destPos := syscall.SizeofRtMsghdr + *(*syscall.RawSockaddrInet4)(unsafe.Pointer(&wb[destPos])) = destAddr + + // Add gateway sockaddr + gwAddr := syscall.RawSockaddrInet4{ + Len: syscall.SizeofSockaddrInet4, + Family: syscall.AF_INET, + } + copy(gwAddr.Addr[:], gateway.To4()) + gwPos := destPos + syscall.SizeofSockaddrInet4 + *(*syscall.RawSockaddrInet4)(unsafe.Pointer(&wb[gwPos])) = gwAddr + + // Add netmask sockaddr + maskAddr := syscall.RawSockaddrInet4{ + Len: syscall.SizeofSockaddrInet4, + Family: syscall.AF_INET, + } + // Create the netmask based on prefix length + if prefixLen > 32 { + prefixLen = 32 + } + for i := 0; i < prefixLen/8; i++ { + maskAddr.Addr[i] = 0xff + } + if prefixLen%8 != 0 { + maskAddr.Addr[prefixLen/8] = ^byte(0xff >> uint(prefixLen%8)) + } + maskPos := gwPos + syscall.SizeofSockaddrInet4 + *(*syscall.RawSockaddrInet4)(unsafe.Pointer(&wb[maskPos])) = maskAddr + + if _, err := syscall.Write(g.routeSocket, wb); err != nil { + return fmt.Errorf("write route message: %w", err) + } + + // Read response + buf := make([]byte, os.Getpagesize()) + n, err := syscall.Read(g.routeSocket, buf) + if err != nil { + return fmt.Errorf("read route message: %w", err) + } + + return g.handleRouteResponse(buf, n, "add") +} + +func (g *GatewayMonitor) deleteRouteSyscall(dest net.IP) error { + // Create routing message + rtmsg := &syscall.RtMsghdr{ + Type: syscall.RTM_DELETE, + Version: syscall.RTM_VERSION, + Seq: g.seq, + Addrs: syscall.RTA_DST | syscall.RTA_NETMASK, + Pid: 0, + Flags: syscall.RTF_UP | syscall.RTF_HOST | syscall.RTF_GATEWAY | syscall.RTF_STATIC, + } + g.seq++ + + // Calculate total message size: header + destination + netmask + msgLen := syscall.SizeofRtMsghdr + syscall.SizeofSockaddrInet4*2 + + // Create message buffer + wb := make([]byte, msgLen) + + // Copy header + rtmsg.Msglen = uint16(msgLen) + *(*syscall.RtMsghdr)(unsafe.Pointer(&wb[0])) = *rtmsg + + // Add destination sockaddr + destAddr := syscall.RawSockaddrInet4{ + Len: syscall.SizeofSockaddrInet4, + Family: syscall.AF_INET, + } + copy(destAddr.Addr[:], dest.To4()) + destPos := syscall.SizeofRtMsghdr + *(*syscall.RawSockaddrInet4)(unsafe.Pointer(&wb[destPos])) = destAddr + + // Add netmask sockaddr (full mask for host route) + maskAddr := syscall.RawSockaddrInet4{ + Len: syscall.SizeofSockaddrInet4, + Family: syscall.AF_INET, + Addr: [4]byte{255, 255, 255, 255}, // /32 netmask + } + maskPos := destPos + syscall.SizeofSockaddrInet4 + *(*syscall.RawSockaddrInet4)(unsafe.Pointer(&wb[maskPos])) = maskAddr + + // Create a new route socket for deletion + sock, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC) + if err != nil { + return fmt.Errorf("create socket: %w", err) + } + defer syscall.Close(sock) + + // Set socket options + err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_USELOOPBACK, 1) + if err != nil { + return fmt.Errorf("set socket options: %w", err) + } + + // Write the delete message + if _, err := syscall.Write(sock, wb); err != nil { + return fmt.Errorf("write route message: %w", err) + } + + // Read response + rb := make([]byte, os.Getpagesize()) + n, err := syscall.Read(sock, rb) + if err != nil { + return fmt.Errorf("read route message: %w", err) + } + + if n < syscall.SizeofRtMsghdr { + return fmt.Errorf("short read: got %d bytes", n) + } + + // Parse response header + rtm := (*syscall.RtMsghdr)(unsafe.Pointer(&rb[0])) + if rtm.Version != syscall.RTM_VERSION { + return fmt.Errorf("invalid routing message version: %d", rtm.Version) + } + + // Check for errors + if rtm.Type == rtmError { + errno := *(*int32)(unsafe.Pointer(&rb[syscall.SizeofRtMsghdr])) + if errno != 0 { + if errno == int32(syscall.ESRCH) { + // Route not found is not an error + return nil + } + return fmt.Errorf("route delete failed: %w", syscall.Errno(errno)) + } + } + + g.logger.Info("Successfully deleted route using syscall", + zap.String("destination", dest.String())) + return nil +} + +func (g *GatewayMonitor) handleRouteResponse(buf []byte, n int, op string) error { + if n < syscall.SizeofRtMsghdr { + return fmt.Errorf("short read: got %d bytes", n) + } + + rtm := (*syscall.RtMsghdr)(unsafe.Pointer(&buf[0])) + if rtm.Version != syscall.RTM_VERSION { + return fmt.Errorf("invalid routing message version: %d", rtm.Version) + } + + // Check for errors first + if rtm.Type == rtmError { + errno := *(*int32)(unsafe.Pointer(&buf[syscall.SizeofRtMsghdr])) + if errno != 0 { + return fmt.Errorf("route %s failed: %w", op, syscall.Errno(errno)) } - logger.Info("Command executed", zap.String("output", string(output))) } + + // Check message length after error check + msgLen := int(rtm.Msglen) + if msgLen > n { + return fmt.Errorf("message length %d > read length %d", msgLen, n) + } + return nil } -func addGatewayRoute(cfg *service.Config, logger *zap.Logger, gatewayIP net.IP) error { - logger.Info("Current gateway route:" + gatewayIP.String()) - for _, client := range cfg.Clients { - var commands []string - if gatewayIP.To4() != nil { - // IPv4 gateway - commands = []string{ - "sudo route -n delete -net " + client.ProxyEndpointAddress.IP().String(), - "sudo route -n add -net " + client.ProxyEndpointAddress.IP().String() + "/32 -gateway " + gatewayIP.String(), +func (g *GatewayMonitor) verifyRoutesSyscall(gatewayIP net.IP) (map[string]bool, error) { + g.logger.Info("Verifying routes using syscall") + + routes := make(map[string]bool) + + // Open a route socket + fd, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC) + if err != nil { + g.logger.Error("Failed to open route socket", + zap.Error(err)) + return nil, err + } + defer syscall.Close(fd) + + // Get the routing table + tab, err := syscall.RouteRIB(syscall.NET_RT_DUMP2, 0) + if err != nil { + g.logger.Error("Failed to get routing table", + zap.Error(err)) + return nil, err + } + + // Parse the routing messages + msgs, err := syscall.ParseRoutingMessage(tab) + if err != nil { + g.logger.Error("Failed to parse routing messages", + zap.Error(err)) + return nil, err + } + + // Process each routing message + for _, msg := range msgs { + rmsg, ok := msg.(*syscall.RouteMessage) + if !ok { + continue + } + + // Get addresses from the message + data := rmsg.Data[:] + for len(data) > 0 { + alen := int(data[0]) + if alen < 4 { + // Malformed address + break } - } else { - // IPv6 gateway - commands = []string{ - "sudo route -n delete -inet6 -net " + client.ProxyEndpointAddress.IP().String(), - "sudo route -n add -inet6 -net " + client.ProxyEndpointAddress.IP().String() + "/128 -gateway " + gatewayIP.String(), + + if len(data) < alen { + // Message too short + break } + + // For IPv4 addresses + if alen >= 8 { + ip := net.IP(data[4:8]) + if ip.Equal(gatewayIP) { + routes[ip.String()] = true + } + } + + data = data[alen:] } - err := executeCommands(logger, commands) + } + + return routes, nil +} + +// updateRoutes updates all client routes with the new gateway +func (g *GatewayMonitor) updateRoutes(gatewayIP net.IP) error { + if gatewayIP == nil { + return ErrInvalidGateway + } + + g.logger.Info("Updating gateway routes", zap.String("gateway", gatewayIP.String())) + + for _, client := range g.cfg.Clients { + clientAddr := client.ProxyEndpointAddress.IP() + clientIP := net.IP(clientAddr.AsSlice()) + + // First try to delete any existing route + err := g.deleteRouteSyscall(clientIP) + if err != nil { + g.logger.Debug("Route deletion failed (may not exist)", + zap.String("client", clientAddr.String()), + zap.Error(err)) + } + + // Add the new route + err = g.addRoute(clientIP, gatewayIP, 32) if err != nil { - return fmt.Errorf("failed to recreate route for client %s: %v", client.ProxyEndpointAddress.IP().String(), err) + return fmt.Errorf("add route for client %s: %w", clientIP, err) } } + return nil } -func deleteGatewayRoute(cfg *service.Config, logger *zap.Logger) { - for _, client := range cfg.Clients { - err := executeCommands(logger, []string{"sudo route -n delete -net " + client.ProxyEndpointAddress.IP().String()}) +func (g *GatewayMonitor) cleanup() { + g.logger.Info("Cleaning up gateway routes") + + // Get current routes first + routes, err := g.verifyRoutesSyscall(g.ip) + if err != nil { + g.logger.Warn("Failed to get current routes during cleanup", zap.Error(err)) + } + + maxRetries := 3 + for retry := 0; retry < maxRetries; retry++ { + allDeleted := true + + for _, client := range g.cfg.Clients { + clientAddr := client.ProxyEndpointAddress.IP() + clientIP := net.IP(clientAddr.AsSlice()) + + // Check if route exists + if routes != nil { + if _, exists := routes[clientIP.String()]; !exists { + continue // Route doesn't exist, skip + } + } + + if err := g.deleteRouteSyscall(clientIP); err != nil { + // Only log as error if it's not "no such route" + if !strings.Contains(err.Error(), "no such process") { + g.logger.Error("Failed to delete route", + zap.String("client", clientAddr.String()), + zap.Error(err)) + allDeleted = false + } + } else { + g.logger.Info("Successfully deleted route", + zap.String("client", clientAddr.String())) + } + } + + if allDeleted { + g.logger.Info("All routes deleted successfully") + return + } + + // If not all routes were deleted, wait a bit and verify routes again + time.Sleep(100 * time.Millisecond) + routes, err = g.verifyRoutesSyscall(g.ip) if err != nil { - logger.Error("Failed to delete route:", zap.Error(err)) + g.logger.Warn("Failed to verify routes during cleanup retry", + zap.Int("retry", retry+1), + zap.Error(err)) } } + + g.logger.Warn("Some routes may not have been deleted after all retries") } -var macGateway = gatewayMonitor{} +// Start begins monitoring the gateway +func (g *GatewayMonitor) Start() error { + gatewayIP, err := g.discoverGateway() + if err != nil { + return fmt.Errorf("initial gateway discovery: %w", err) + } + + if err := g.updateRoutes(gatewayIP); err != nil { + return fmt.Errorf("initial route update: %w", err) + } + + g.mu.Lock() + g.ip = gatewayIP + g.mu.Unlock() + + go g.watch() + return nil +} -type gatewayMonitor struct { - ip net.IP - logger *zap.Logger - cfg *service.Config - cancelled chan struct{} +// Stop halts the gateway monitoring and cleans up routes +func (g *GatewayMonitor) Stop() { + g.cancel() + g.cleanup() + syscall.Close(g.routeSocket) } -func (g *gatewayMonitor) watch() { +func (g *GatewayMonitor) watch() { + ticker := time.NewTicker(g.interval) + defer ticker.Stop() + + var lastValidGateway net.IP + var consecutiveErrors int + for { select { - case <-g.cancelled: + case <-g.ctx.Done(): return - default: - ip, err := discoverGateway() + case <-ticker.C: + ip, err := g.discoverGateway() if err != nil { - g.logger.Error("Failed to get Gateway address:", zap.Error(err)) - } - if !g.ip.Equal(ip) { - g.logger.Info("Gateway address changed, reconfiguring routes") - err = addGatewayRoute(g.cfg, g.logger, ip) - if err != nil { - g.logger.Error("Failed to reconfigure routes:", zap.Error(err)) + consecutiveErrors++ + if consecutiveErrors > 3 { + g.logger.Error("Failed to get gateway address", zap.Error(err)) + } else { + g.logger.Debug("Temporary error getting gateway", zap.Error(err)) } - // update ip - g.ip = ip + // If we have a last valid gateway, keep using it + if lastValidGateway != nil { + ip = lastValidGateway + } else { + continue + } + } else { + consecutiveErrors = 0 } - // sleep for 10 seconds - time.Sleep(10 * time.Second) + g.mu.Lock() + gatewayChanged := !ip.Equal(g.ip) + if gatewayChanged { + g.logger.Info("Gateway IP changed", + zap.String("old", g.ip.String()), + zap.String("new", ip.String())) + + // Delete old routes before updating the gateway IP + g.logger.Info("Cleaning up old routes") + g.cleanup() + + // Update gateway IP + g.ip = ip + lastValidGateway = ip + + // Add new routes + if err := g.updateRoutes(ip); err != nil { + g.logger.Error("Failed to update routes", zap.Error(err)) + } + } + g.mu.Unlock() } } } -// add route +var monitor *GatewayMonitor + +// Initialize sets up the gateway monitor func initHook(cfg *service.Config, logger *zap.Logger) { - gatewayIp, err := discoverGateway() + var err error + monitor, err = NewGatewayMonitor(cfg, logger, 10*time.Second) if err != nil { - logger.Fatal("Failed to get Gateway address:", zap.Error(err)) + logger.Fatal("Failed to create gateway monitor", zap.Error(err)) } - err = addGatewayRoute(cfg, logger, gatewayIp) - if err != nil { - logger.Fatal("Falied to add gateway route:", zap.Error(err)) + + if err := monitor.Start(); err != nil { + logger.Fatal("Failed to start gateway monitor", zap.Error(err)) } - macGateway.ip = gatewayIp - macGateway.logger = logger - macGateway.cfg = cfg - macGateway.cancelled = make(chan struct{}) - go macGateway.watch() } -func cleanupHook(cfg *service.Config, logger *zap.Logger) { - deleteGatewayRoute(cfg, logger) - - // cancel gateway monitor - close(macGateway.cancelled) +// Cleanup performs necessary cleanup +func cleanupHook() { + if monitor != nil { + monitor.Stop() + } } diff --git a/cmd/swgp-go/main_linux.go b/cmd/swgp-go/main_linux.go index acaf1b9..964c6fe 100644 --- a/cmd/swgp-go/main_linux.go +++ b/cmd/swgp-go/main_linux.go @@ -11,6 +11,6 @@ func initHook(config service.Config, logger *zap.Logger) { // NOOP } -func cleanerHook(config service.Config, logger *zap.Logger) { +func cleanerHook() { // NOOP } diff --git a/go.mod b/go.mod index cabe343..9d996ab 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/lmittmann/tint v1.0.6 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.31.0 - golang.org/x/net v0.21.0 + golang.org/x/net v0.32.0 golang.org/x/sys v0.28.0 ) diff --git a/go.sum b/go.sum index ce53533..48de2e8 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From a51d59e65f419d182f857891cd633d66004cf9ce Mon Sep 17 00:00:00 2001 From: Yusup Date: Thu, 19 Dec 2024 03:01:00 +0800 Subject: [PATCH 6/9] cleanup --- cmd/swgp-go/main_darwin.go | 57 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go index d6367d8..796a98a 100644 --- a/cmd/swgp-go/main_darwin.go +++ b/cmd/swgp-go/main_darwin.go @@ -6,6 +6,8 @@ import ( "context" "errors" "fmt" + "github.com/database64128/swgp-go/tslog" + "log/slog" "net" "os" "strings" @@ -15,7 +17,6 @@ import ( "unsafe" "github.com/database64128/swgp-go/service" - "go.uber.org/zap" "golang.org/x/net/route" ) @@ -28,7 +29,7 @@ var ( type GatewayMonitor struct { mu sync.RWMutex ip net.IP - logger *zap.Logger + logger *tslog.Logger cfg *service.Config ctx context.Context cancel context.CancelFunc @@ -38,7 +39,7 @@ type GatewayMonitor struct { } // NewGatewayMonitor creates a new gateway monitor instance -func NewGatewayMonitor(cfg *service.Config, logger *zap.Logger, interval time.Duration) (*GatewayMonitor, error) { +func NewGatewayMonitor(cfg *service.Config, logger *tslog.Logger, interval time.Duration) (*GatewayMonitor, error) { ctx, cancel := context.WithCancel(context.Background()) // Open routing socket @@ -120,13 +121,13 @@ func (g *GatewayMonitor) discoverGateway() (net.IP, error) { } } if len(ips) > 0 { - g.logger.Info("Found gateway", zap.String("ip", ips[0].String())) + g.logger.Info("Found gateway", slog.String("ip", ips[0].String())) return ips[0], nil } return nil, fmt.Errorf("no default gateway found") } -func isValidGateway(ip net.IP, logger *zap.Logger) bool { +func isValidGateway(ip net.IP, logger *tslog.Logger) bool { if ip == nil || ip.Equal(net.IPv4zero) { logger.Debug("Invalid gateway: nil or zero IP") return false @@ -143,7 +144,7 @@ func isValidGateway(ip net.IP, logger *zap.Logger) bool { for _, network := range privateNetworks { _, ipnet, err := net.ParseCIDR(network) if err != nil { - logger.Error("Failed to parse CIDR", zap.String("network", network), zap.Error(err)) + logger.Error("Failed to parse CIDR", slog.String("network", network), tslog.Err(err)) continue } if ipnet.Contains(ip) { @@ -151,7 +152,7 @@ func isValidGateway(ip net.IP, logger *zap.Logger) bool { } } - logger.Debug("Invalid gateway: not in private or link-local range", zap.String("ip", ip.String())) + logger.Debug("Invalid gateway: not in private or link-local range", slog.String("ip", ip.String())) return false } @@ -319,7 +320,7 @@ func (g *GatewayMonitor) deleteRouteSyscall(dest net.IP) error { } g.logger.Info("Successfully deleted route using syscall", - zap.String("destination", dest.String())) + slog.String("destination", dest.String())) return nil } @@ -359,7 +360,7 @@ func (g *GatewayMonitor) verifyRoutesSyscall(gatewayIP net.IP) (map[string]bool, fd, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC) if err != nil { g.logger.Error("Failed to open route socket", - zap.Error(err)) + tslog.Err(err)) return nil, err } defer syscall.Close(fd) @@ -368,7 +369,7 @@ func (g *GatewayMonitor) verifyRoutesSyscall(gatewayIP net.IP) (map[string]bool, tab, err := syscall.RouteRIB(syscall.NET_RT_DUMP2, 0) if err != nil { g.logger.Error("Failed to get routing table", - zap.Error(err)) + tslog.Err(err)) return nil, err } @@ -376,7 +377,7 @@ func (g *GatewayMonitor) verifyRoutesSyscall(gatewayIP net.IP) (map[string]bool, msgs, err := syscall.ParseRoutingMessage(tab) if err != nil { g.logger.Error("Failed to parse routing messages", - zap.Error(err)) + tslog.Err(err)) return nil, err } @@ -422,7 +423,7 @@ func (g *GatewayMonitor) updateRoutes(gatewayIP net.IP) error { return ErrInvalidGateway } - g.logger.Info("Updating gateway routes", zap.String("gateway", gatewayIP.String())) + g.logger.Info("Updating gateway routes", slog.String("gateway", gatewayIP.String())) for _, client := range g.cfg.Clients { clientAddr := client.ProxyEndpointAddress.IP() @@ -432,8 +433,8 @@ func (g *GatewayMonitor) updateRoutes(gatewayIP net.IP) error { err := g.deleteRouteSyscall(clientIP) if err != nil { g.logger.Debug("Route deletion failed (may not exist)", - zap.String("client", clientAddr.String()), - zap.Error(err)) + slog.String("client", clientAddr.String()), + tslog.Err(err)) } // Add the new route @@ -452,7 +453,7 @@ func (g *GatewayMonitor) cleanup() { // Get current routes first routes, err := g.verifyRoutesSyscall(g.ip) if err != nil { - g.logger.Warn("Failed to get current routes during cleanup", zap.Error(err)) + g.logger.Warn("Failed to get current routes during cleanup", tslog.Err(err)) } maxRetries := 3 @@ -474,13 +475,13 @@ func (g *GatewayMonitor) cleanup() { // Only log as error if it's not "no such route" if !strings.Contains(err.Error(), "no such process") { g.logger.Error("Failed to delete route", - zap.String("client", clientAddr.String()), - zap.Error(err)) + slog.String("client", clientAddr.String()), + tslog.Err(err)) allDeleted = false } } else { g.logger.Info("Successfully deleted route", - zap.String("client", clientAddr.String())) + slog.String("client", clientAddr.String())) } } @@ -494,8 +495,8 @@ func (g *GatewayMonitor) cleanup() { routes, err = g.verifyRoutesSyscall(g.ip) if err != nil { g.logger.Warn("Failed to verify routes during cleanup retry", - zap.Int("retry", retry+1), - zap.Error(err)) + slog.Int("retry", retry+1), + tslog.Err(err)) } } @@ -544,9 +545,9 @@ func (g *GatewayMonitor) watch() { if err != nil { consecutiveErrors++ if consecutiveErrors > 3 { - g.logger.Error("Failed to get gateway address", zap.Error(err)) + g.logger.Error("Failed to get gateway address", tslog.Err(err)) } else { - g.logger.Debug("Temporary error getting gateway", zap.Error(err)) + g.logger.Debug("Temporary error getting gateway", tslog.Err(err)) } // If we have a last valid gateway, keep using it @@ -563,8 +564,8 @@ func (g *GatewayMonitor) watch() { gatewayChanged := !ip.Equal(g.ip) if gatewayChanged { g.logger.Info("Gateway IP changed", - zap.String("old", g.ip.String()), - zap.String("new", ip.String())) + slog.String("old", g.ip.String()), + slog.String("new", ip.String())) // Delete old routes before updating the gateway IP g.logger.Info("Cleaning up old routes") @@ -576,7 +577,7 @@ func (g *GatewayMonitor) watch() { // Add new routes if err := g.updateRoutes(ip); err != nil { - g.logger.Error("Failed to update routes", zap.Error(err)) + g.logger.Error("Failed to update routes", tslog.Err(err)) } } g.mu.Unlock() @@ -587,15 +588,15 @@ func (g *GatewayMonitor) watch() { var monitor *GatewayMonitor // Initialize sets up the gateway monitor -func initHook(cfg *service.Config, logger *zap.Logger) { +func initHook(cfg *service.Config, logger *tslog.Logger) { var err error monitor, err = NewGatewayMonitor(cfg, logger, 10*time.Second) if err != nil { - logger.Fatal("Failed to create gateway monitor", zap.Error(err)) + panic(err) } if err := monitor.Start(); err != nil { - logger.Fatal("Failed to start gateway monitor", zap.Error(err)) + panic(err) } } From 8922c4e19e45ec4199f53eff2352c709718a543c Mon Sep 17 00:00:00 2001 From: Yusup Date: Thu, 19 Dec 2024 03:03:58 +0800 Subject: [PATCH 7/9] fix logs --- cmd/swgp-go/main_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/swgp-go/main_linux.go b/cmd/swgp-go/main_linux.go index 964c6fe..fab27ba 100644 --- a/cmd/swgp-go/main_linux.go +++ b/cmd/swgp-go/main_linux.go @@ -4,10 +4,10 @@ package main import ( "github.com/database64128/swgp-go/service" - "go.uber.org/zap" + "github.com/database64128/swgp-go/tslog" ) -func initHook(config service.Config, logger *zap.Logger) { +func initHook(config service.Config, logger *tslog.Logger) { // NOOP } From 56d911eb976092ee8c8e67db8f9e75b7d8822dcb Mon Sep 17 00:00:00 2001 From: Yusup Date: Thu, 19 Dec 2024 03:12:03 +0800 Subject: [PATCH 8/9] tidy --- go.mod | 3 --- go.sum | 14 -------------- 2 files changed, 17 deletions(-) diff --git a/go.mod b/go.mod index 9d996ab..ed63156 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,7 @@ go 1.23.0 require ( github.com/database64128/netx-go v0.0.0-20241205055133-3d4b4d263f10 github.com/lmittmann/tint v1.0.6 - go.uber.org/zap v1.27.0 golang.org/x/crypto v0.31.0 golang.org/x/net v0.32.0 golang.org/x/sys v0.28.0 ) - -require go.uber.org/multierr v1.10.0 // indirect diff --git a/go.sum b/go.sum index 48de2e8..e6343f0 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,10 @@ github.com/database64128/netx-go v0.0.0-20241205055133-3d4b4d263f10 h1:UJId3liaDh+tlJ1e3OmXqIevs9JFYXXo1K30Yx/nkrc= github.com/database64128/netx-go v0.0.0-20241205055133-3d4b4d263f10/go.mod h1:dqHsLB0Fb36Z2NSrzKklBf27+hLifGwPEGcGGXib3Rw= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc= github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 471c94fa4bf54a99c56da2b5053b19598b281ad2 Mon Sep 17 00:00:00 2001 From: Yusup Date: Sun, 22 Dec 2024 15:01:00 +0800 Subject: [PATCH 9/9] cleanup --- cmd/swgp-go/main_darwin.go | 124 ++++++++++++++----------------------- 1 file changed, 46 insertions(+), 78 deletions(-) diff --git a/cmd/swgp-go/main_darwin.go b/cmd/swgp-go/main_darwin.go index 796a98a..0e83d78 100644 --- a/cmd/swgp-go/main_darwin.go +++ b/cmd/swgp-go/main_darwin.go @@ -79,15 +79,8 @@ func NewGatewayMonitor(cfg *service.Config, logger *tslog.Logger, interval time. return monitor, nil } -// roundup rounds up length to the nearest multiple of 4 -func roundup(length int) int { - if length == 0 { - return 0 - } - return ((length) + 3) &^ 3 -} - -func (g *GatewayMonitor) discoverGateway() (net.IP, error) { +// getRouteTable returns the routing table messages using x/net/route package +func (g *GatewayMonitor) getRouteTable() ([]*route.RouteMessage, error) { rib, err := route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0) if err != nil { return nil, fmt.Errorf("failed to fetch RIB: %v", err) @@ -98,24 +91,36 @@ func (g *GatewayMonitor) discoverGateway() (net.IP, error) { return nil, fmt.Errorf("failed to parse RIB: %v", err) } - var ips []net.IP - + var routeMsgs []*route.RouteMessage for _, m := range msgs { if rm, ok := m.(*route.RouteMessage); ok { - if rm.Flags&syscall.RTF_GATEWAY != 0 && rm.Flags&syscall.RTF_UP != 0 { - addr := rm.Addrs[syscall.RTAX_GATEWAY] - switch sa := addr.(type) { - case *route.Inet4Addr: - ip := net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) - if isValidGateway(ip, g.logger) { - ips = append(ips, ip) - } - case *route.Inet6Addr: - ip := make(net.IP, net.IPv6len) - copy(ip, sa.IP[:]) - if isValidGateway(ip, g.logger) { - ips = append(ips, ip) - } + routeMsgs = append(routeMsgs, rm) + } + } + return routeMsgs, nil +} + +func (g *GatewayMonitor) discoverGateway() (net.IP, error) { + msgs, err := g.getRouteTable() + if err != nil { + return nil, err + } + + var ips []net.IP + for _, rm := range msgs { + if rm.Flags&syscall.RTF_GATEWAY != 0 && rm.Flags&syscall.RTF_UP != 0 { + addr := rm.Addrs[syscall.RTAX_GATEWAY] + switch sa := addr.(type) { + case *route.Inet4Addr: + ip := net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) + if isValidGateway(ip, g.logger) { + ips = append(ips, ip) + } + case *route.Inet6Addr: + ip := make(net.IP, net.IPv6len) + copy(ip, sa.IP[:]) + if isValidGateway(ip, g.logger) { + ips = append(ips, ip) } } } @@ -352,65 +357,28 @@ func (g *GatewayMonitor) handleRouteResponse(buf []byte, n int, op string) error } func (g *GatewayMonitor) verifyRoutesSyscall(gatewayIP net.IP) (map[string]bool, error) { - g.logger.Info("Verifying routes using syscall") - - routes := make(map[string]bool) - - // Open a route socket - fd, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC) - if err != nil { - g.logger.Error("Failed to open route socket", - tslog.Err(err)) - return nil, err - } - defer syscall.Close(fd) - - // Get the routing table - tab, err := syscall.RouteRIB(syscall.NET_RT_DUMP2, 0) - if err != nil { - g.logger.Error("Failed to get routing table", - tslog.Err(err)) - return nil, err - } - - // Parse the routing messages - msgs, err := syscall.ParseRoutingMessage(tab) + g.logger.Info("Verifying routes") + + msgs, err := g.getRouteTable() if err != nil { - g.logger.Error("Failed to parse routing messages", - tslog.Err(err)) return nil, err } - // Process each routing message - for _, msg := range msgs { - rmsg, ok := msg.(*syscall.RouteMessage) - if !ok { - continue - } - - // Get addresses from the message - data := rmsg.Data[:] - for len(data) > 0 { - alen := int(data[0]) - if alen < 4 { - // Malformed address - break - } - - if len(data) < alen { - // Message too short - break + routes := make(map[string]bool) + for _, rm := range msgs { + addr := rm.Addrs[syscall.RTAX_GATEWAY] + switch sa := addr.(type) { + case *route.Inet4Addr: + ip := net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) + if ip.Equal(gatewayIP) { + routes[ip.String()] = true } - - // For IPv4 addresses - if alen >= 8 { - ip := net.IP(data[4:8]) - if ip.Equal(gatewayIP) { - routes[ip.String()] = true - } + case *route.Inet6Addr: + ip := make(net.IP, net.IPv6len) + copy(ip, sa.IP[:]) + if ip.Equal(gatewayIP) { + routes[ip.String()] = true } - - data = data[alen:] } }