From 3f6246d5bb8160bf7b151f5c7be890e53f215041 Mon Sep 17 00:00:00 2001 From: netbyte Date: Sat, 27 Aug 2022 02:26:00 +0800 Subject: [PATCH] init --- .gitignore | 2 + .travis.yml | 5 ++ LICENSE | 20 ++++++ README.md | 14 ++++ gateway.go | 23 +++++++ gateway_darwin.go | 27 ++++++++ gateway_linux.go | 26 ++++++++ gateway_parsers.go | 141 +++++++++++++++++++++++++++++++++++++++ gateway_unimplemented.go | 16 +++++ gateway_windows.go | 32 +++++++++ go.mod | 3 + 11 files changed, 309 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100755 LICENSE create mode 100644 README.md create mode 100644 gateway.go create mode 100644 gateway_darwin.go create mode 100644 gateway_linux.go create mode 100644 gateway_parsers.go create mode 100644 gateway_unimplemented.go create mode 100644 gateway_windows.go create mode 100644 go.mod diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aafda3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +.idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a20e391 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - 1.18 +script: + - go test -v ./... \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..69f733f --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d306c7f --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# go-gateway + +A simple library for discovering the IP address of the default gateway. + + +[![Build Status](https://travis-ci.org/net-byte/go-gateway.svg)](https://travis-ci.org/net-byte/go-gateway) + +Provides implementations for: + ++ Linux ++ MacOS ++ Windows + +Other platforms use an implementation that always returns an error. diff --git a/gateway.go b/gateway.go new file mode 100644 index 0000000..dc498db --- /dev/null +++ b/gateway.go @@ -0,0 +1,23 @@ +package gateway + +import ( + "errors" + "net" + "runtime" +) + +var ( + errNoGateway = errors.New("no gateway found") + errCantParse = errors.New("can't parse string output") + errNotImplemented = errors.New("not implemented for OS: " + runtime.GOOS) +) + +// DiscoverGatewayIPv4 is the OS independent function to get the default ipv4 gateway +func DiscoverGatewayIPv4() (ip net.IP, err error) { + return discoverGatewayOSSpecificIPv4() +} + +// DiscoverGatewayIPv6 is the OS independent function to get the default ipv6 gateway +func DiscoverGatewayIPv6() (ip net.IP, err error) { + return discoverGatewayOSSpecificIPv6() +} diff --git a/gateway_darwin.go b/gateway_darwin.go new file mode 100644 index 0000000..a1af0cb --- /dev/null +++ b/gateway_darwin.go @@ -0,0 +1,27 @@ +//go:build darwin +// +build darwin + +package gateway + +import ( + "net" + "os/exec" +) + +func discoverGatewayOSSpecificIPv4() (ip net.IP, err error) { + ipstr := execCmd("sh", "-c", "route -n get default | grep 'gateway' | awk 'NR==1{print $2}'") + ipv4 := net.ParseIP(ipstr) + if ipv4 == nil { + return nil, errCantParse + } + return ipv4, nil +} + +func discoverGatewayOSSpecificIPv6() (ip net.IP, err error) { + ipstr := execCmd("sh", "-c", "route -6 -n get default | grep 'gateway' | awk 'NR==1{print $2}'") + ipv6 := net.ParseIP(ipstr) + if ipv6 == nil { + return nil, errCantParse + } + return ipv6, nil +} diff --git a/gateway_linux.go b/gateway_linux.go new file mode 100644 index 0000000..6b758ec --- /dev/null +++ b/gateway_linux.go @@ -0,0 +1,26 @@ +//go:build linux +// +build linux + +package gateway + +import ( + "net" +) + +func discoverGatewayOSSpecificIPv4() (ip net.IP, err error) { + ipstr := execCmd("sh", "-c", "route -n | grep 'UG[ \t]' | awk 'NR==1{print $2}'") + ipv4 := net.ParseIP(ipstr) + if ipv4 == nil { + return nil, errCantParse + } + return ipv4, nil +} + +func discoverGatewayOSSpecificIPv6() (ip net.IP, err error) { + ipstr := execCmd("sh", "-c", "route -6 -n | grep 'UG[ \t]' | awk 'NR==1{print $2}'") + ipv6 := net.ParseIP(ipstr) + if ipv6 == nil { + return nil, errCantParse + } + return ipv6, nil +} diff --git a/gateway_parsers.go b/gateway_parsers.go new file mode 100644 index 0000000..121b41e --- /dev/null +++ b/gateway_parsers.go @@ -0,0 +1,141 @@ +package gateway + +import ( + "log" + "net" + "os/exec" + "strings" +) + +type windowsRouteStructIPv4 struct { + Destination string + Netmask string + Gateway string + Interface string + Metric string +} + +type windowsRouteStructIPv6 struct { + If string + Metric string + Destination string + Gateway string +} + +func parseToWindowsRouteStructIPv4(output []byte) (windowsRouteStructIPv4, error) { + // Windows route output format is always like this: + // =========================================================================== + // Interface List + // 8 ...00 12 3f a7 17 ba ...... Intel(R) PRO/100 VE Network Connection + // 1 ........................... Software Loopback Interface 1 + // =========================================================================== + // IPv4 Route Table + // =========================================================================== + // Active Routes: + // Network Destination Netmask Gateway Interface Metric + // 0.0.0.0 0.0.0.0 192.168.1.1 192.168.1.100 20 + // =========================================================================== + // + // Windows commands are localized, so we can't just look for "Active Routes:" string + // I'm trying to pick the active route, + // then jump 2 lines and get the row + // Not using regex because output is quite standard from Windows XP to 8 (NEEDS TESTING) + lines := strings.Split(string(output), "\n") + sep := 0 + for idx, line := range lines { + if sep == 3 { + // We just entered the 2nd section containing "Active Routes:" + if len(lines) <= idx+2 { + return windowsRouteStructIPv4{}, errNoGateway + } + + fields := strings.Fields(lines[idx+2]) + if len(fields) < 5 { + return windowsRouteStructIPv4{}, errCantParse + } + + return windowsRouteStructIPv4{ + Destination: fields[0], + Netmask: fields[1], + Gateway: fields[2], + Interface: fields[3], + Metric: fields[4], + }, nil + } + if strings.HasPrefix(line, "=======") { + sep++ + continue + } + } + return windowsRouteStructIPv4{}, errNoGateway +} + +func parseToWindowsRouteStructIPv6(output []byte) (windowsRouteStructIPv6, error) { + + lines := strings.Split(string(output), "\n") + sep := 0 + for idx, line := range lines { + if sep == 3 { + // We just entered the 2nd section containing "Active Routes:" + if len(lines) <= idx+2 { + return windowsRouteStructIPv6{}, errNoGateway + } + + fields := strings.Fields(lines[idx+2]) + if len(fields) < 5 { + return windowsRouteStructIPv6{}, errCantParse + } + + return windowsRouteStructIPv6{ + If: fields[0], + Metric: fields[1], + Destination: fields[2], + Gateway: fields[3], + }, nil + } + if strings.HasPrefix(line, "=======") { + sep++ + continue + } + } + return windowsRouteStructIPv6{}, errNoGateway +} + +func parseWindowsGatewayIPv4(output []byte) (net.IP, error) { + parsedOutput, err := parseToWindowsRouteStructIPv4(output) + if err != nil { + return nil, err + } + + ip := net.ParseIP(parsedOutput.Gateway) + if ip == nil { + return nil, errCantParse + } + return ip, nil +} + +func parseWindowsGatewayIPv6(output []byte) (net.IP, error) { + parsedOutput, err := parseToWindowsRouteStructIPv6(output) + if err != nil { + return nil, err + } + + ip := net.ParseIP(parsedOutput.Gateway) + if ip == nil { + return nil, errCantParse + } + return ip, nil +} + +func execCmd(c string, args ...string) string { + cmd := exec.Command(c, args...) + out, err := cmd.Output() + if err != nil { + log.Println("failed to exec cmd:", err) + } + if len(out) == 0 { + return "" + } + s := string(out) + return strings.ReplaceAll(s, "\n", "") +} diff --git a/gateway_unimplemented.go b/gateway_unimplemented.go new file mode 100644 index 0000000..b3248de --- /dev/null +++ b/gateway_unimplemented.go @@ -0,0 +1,16 @@ +//go:build !darwin && !linux && !windows && !solaris && !freebsd +// +build !darwin,!linux,!windows,!solaris,!freebsd + +package gateway + +import ( + "net" +) + +func discoverGatewayOSSpecificIPv4() (ip net.IP, err error) { + return ip, errNotImplemented +} + +func discoverGatewayOSSpecificIPv6() (ip net.IP, err error) { + return ip, errNotImplemented +} diff --git a/gateway_windows.go b/gateway_windows.go new file mode 100644 index 0000000..7a113bd --- /dev/null +++ b/gateway_windows.go @@ -0,0 +1,32 @@ +//go:build windows +// +build windows + +package gateway + +import ( + "net" + "os/exec" + "syscall" +) + +func discoverGatewayOSSpecificIPv4() (ip net.IP, err error) { + routeCmd := exec.Command("route", "print", "0.0.0.0") + routeCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, err + } + + return parseWindowsGatewayIPv4(output) +} + +func discoverGatewayOSSpecificIPv6() (ip net.IP, err error) { + routeCmd := exec.Command("route", "print","-6" "::/0") + routeCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, err + } + + return parseWindowsGatewayIPv6(output) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c69f8df --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/net-byte/go-gateway + +go 1.18