├── .gitignore ├── go.mod ├── go.sum ├── gateway_unimplemented.go ├── gateway_solaris.go ├── gateway.go ├── gateway_windows.go ├── gateway_linux.go ├── gatewayForBSDs.go ├── README.md ├── LICENSE ├── gateway_parsers.go └── gateway_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jackpal/gateway 2 | 3 | go 1.20 4 | 5 | require golang.org/x/net v0.8.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 2 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 3 | -------------------------------------------------------------------------------- /gateway_unimplemented.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows 2 | // +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows 3 | 4 | package gateway 5 | 6 | import ( 7 | "net" 8 | ) 9 | 10 | func discoverGatewayOSSpecific() (ip net.IP, err error) { 11 | return ip, errNotImplemented 12 | } 13 | 14 | func discoverGatewayInterfaceOSSpecific() (ip net.IP, err error) { 15 | return nil, errNotImplemented 16 | } 17 | -------------------------------------------------------------------------------- /gateway_solaris.go: -------------------------------------------------------------------------------- 1 | // +build solaris 2 | 3 | package gateway 4 | 5 | import ( 6 | "net" 7 | "os/exec" 8 | ) 9 | 10 | func discoverGatewayOSSpecific() (ip net.IP, err error) { 11 | routeCmd := exec.Command("netstat", "-rn") 12 | output, err := routeCmd.CombinedOutput() 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | return parseBSDSolarisNetstat(output) 18 | } 19 | 20 | func discoverGatewayInterfaceOSSpecific() (ip net.IP, err error) { 21 | return nil, errNotImplemented 22 | } 23 | -------------------------------------------------------------------------------- /gateway.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "runtime" 7 | ) 8 | 9 | var ( 10 | errNoGateway = errors.New("no gateway found") 11 | errCantParse = errors.New("can't parse string output") 12 | errNotImplemented = errors.New("not implemented for OS: " + runtime.GOOS) 13 | ) 14 | 15 | // DiscoverGateway is the OS independent function to get the default gateway 16 | func DiscoverGateway() (ip net.IP, err error) { 17 | return discoverGatewayOSSpecific() 18 | } 19 | 20 | // DiscoverInterface is the OS independent function to call to get the default network interface IP that uses the default gateway 21 | func DiscoverInterface() (ip net.IP, err error) { 22 | return discoverGatewayInterfaceOSSpecific() 23 | } 24 | -------------------------------------------------------------------------------- /gateway_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package gateway 4 | 5 | import ( 6 | "net" 7 | "os/exec" 8 | "syscall" 9 | ) 10 | 11 | func discoverGatewayOSSpecific() (ip net.IP, err error) { 12 | routeCmd := exec.Command("route", "print", "0.0.0.0") 13 | routeCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 14 | output, err := routeCmd.CombinedOutput() 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return parseWindowsGatewayIP(output) 20 | } 21 | 22 | func discoverGatewayInterfaceOSSpecific() (ip net.IP, err error) { 23 | routeCmd := exec.Command("route", "print", "0.0.0.0") 24 | routeCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 25 | output, err := routeCmd.CombinedOutput() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return parseWindowsInterfaceIP(output) 31 | } 32 | -------------------------------------------------------------------------------- /gateway_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package gateway 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "os" 10 | ) 11 | 12 | const ( 13 | // See http://man7.org/linux/man-pages/man8/route.8.html 14 | file = "/proc/net/route" 15 | ) 16 | 17 | func discoverGatewayOSSpecific() (ip net.IP, err error) { 18 | f, err := os.Open(file) 19 | if err != nil { 20 | return nil, fmt.Errorf("Can't access %s", file) 21 | } 22 | defer f.Close() 23 | 24 | bytes, err := ioutil.ReadAll(f) 25 | if err != nil { 26 | return nil, fmt.Errorf("Can't read %s", file) 27 | } 28 | return parseLinuxGatewayIP(bytes) 29 | } 30 | 31 | func discoverGatewayInterfaceOSSpecific() (ip net.IP, err error) { 32 | f, err := os.Open(file) 33 | if err != nil { 34 | return nil, fmt.Errorf("Can't access %s", file) 35 | } 36 | defer f.Close() 37 | 38 | bytes, err := ioutil.ReadAll(f) 39 | if err != nil { 40 | return nil, fmt.Errorf("Can't read %s", file) 41 | } 42 | return parseLinuxInterfaceIP(bytes) 43 | } 44 | -------------------------------------------------------------------------------- /gatewayForBSDs.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 2 | 3 | package gateway 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | 9 | "golang.org/x/net/route" 10 | ) 11 | 12 | func discoverGatewayOSSpecific() (ip net.IP, err error) { 13 | rib, err := route.FetchRIB(syscall.AF_INET, syscall.NET_RT_DUMP, 0) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | msgs, err := route.ParseRIB(syscall.NET_RT_DUMP, rib) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | for _, m := range msgs { 24 | switch m := m.(type) { 25 | case *route.RouteMessage: 26 | var ip net.IP 27 | switch sa := m.Addrs[syscall.RTAX_GATEWAY].(type) { 28 | case *route.Inet4Addr: 29 | ip = net.IPv4(sa.IP[0], sa.IP[1], sa.IP[2], sa.IP[3]) 30 | return ip, nil 31 | case *route.Inet6Addr: 32 | ip = make(net.IP, net.IPv6len) 33 | copy(ip, sa.IP[:]) 34 | return ip, nil 35 | } 36 | } 37 | } 38 | return nil, errNoGateway 39 | } 40 | 41 | func discoverGatewayInterfaceOSSpecific() (ip net.IP, err error) { 42 | return nil, errNotImplemented 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gateway 2 | 3 | A simple library for discovering the IP address of the default gateway. 4 | 5 | Example: 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | 13 | "github.com/jackpal/gateway" 14 | ) 15 | 16 | func main() { 17 | gateway, err := gateway.DiscoverGateway() 18 | if err != nil { 19 | fmt.Println(err) 20 | } else { 21 | fmt.Println("Gateway:", gateway.String()) 22 | } 23 | } 24 | ``` 25 | 26 | Provides implementations for: 27 | 28 | + Darwin (macOS) 29 | + Dragonfly 30 | + FreeBSD 31 | + Linux 32 | + NetBSD 33 | + OpenBSD 34 | + Solaris 35 | + Windows 36 | 37 | Other platforms use an implementation that always returns an error. 38 | 39 | Pull requests for other OSs happily considered! 40 | 41 | ## Versions 42 | 43 | ### v1.0.10 44 | 45 | + Fix non-BSD-based builds. 46 | ### v1.0.9 47 | 48 | + Add go.mod and go.sum files. 49 | + Use "golang.org/x/net/route" to implement all BSD variants. 50 | + As a side effect this adds support for Dragonfly and NetBSD. 51 | + Add example to README. 52 | + Remove broken continuous integration. 53 | 54 | ### v1.0.8 55 | 56 | + Add support for OpenBSD 57 | + Linux parser now supports gateways with an IP address of 0.0.0.0 58 | + Fall back to `netstat` on darwin systems if `route` fails. 59 | + Simplify Linux /proc/net/route parsers. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Jack Palevich. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /gateway_parsers.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "net" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | type windowsRouteStruct struct { 15 | Gateway string 16 | Interface string 17 | } 18 | 19 | type linuxRouteStruct struct { 20 | Iface string 21 | Gateway string 22 | } 23 | 24 | func parseToWindowsRouteStruct(output []byte) (windowsRouteStruct, error) { 25 | // Windows route output format is always like this: 26 | // =========================================================================== 27 | // Interface List 28 | // 8 ...00 12 3f a7 17 ba ...... Intel(R) PRO/100 VE Network Connection 29 | // 1 ........................... Software Loopback Interface 1 30 | // =========================================================================== 31 | // IPv4 Route Table 32 | // =========================================================================== 33 | // Active Routes: 34 | // Network Destination Netmask Gateway Interface Metric 35 | // 0.0.0.0 0.0.0.0 192.168.1.1 192.168.1.100 20 36 | // =========================================================================== 37 | // 38 | // Windows commands are localized, so we can't just look for "Active Routes:" string 39 | // I'm trying to pick the active route, 40 | // then jump 2 lines and get the row 41 | // Not using regex because output is quite standard from Windows XP to 8 (NEEDS TESTING) 42 | lines := strings.Split(string(output), "\n") 43 | sep := 0 44 | for idx, line := range lines { 45 | if sep == 3 { 46 | // We just entered the 2nd section containing "Active Routes:" 47 | if len(lines) <= idx+2 { 48 | return windowsRouteStruct{}, errNoGateway 49 | } 50 | 51 | fields := strings.Fields(lines[idx+2]) 52 | if len(fields) < 5 { 53 | return windowsRouteStruct{}, errCantParse 54 | } 55 | 56 | return windowsRouteStruct{ 57 | Gateway: fields[2], 58 | Interface: fields[3], 59 | }, nil 60 | } 61 | if strings.HasPrefix(line, "=======") { 62 | sep++ 63 | continue 64 | } 65 | } 66 | return windowsRouteStruct{}, errNoGateway 67 | } 68 | 69 | func parseToLinuxRouteStruct(output []byte) (linuxRouteStruct, error) { 70 | // parseLinuxProcNetRoute parses the route file located at /proc/net/route 71 | // and returns the IP address of the default gateway. The default gateway 72 | // is the one with Destination value of 0.0.0.0. 73 | // 74 | // The Linux route file has the following format: 75 | // 76 | // $ cat /proc/net/route 77 | // 78 | // Iface Destination Gateway Flags RefCnt Use Metric Mask 79 | // eno1 00000000 C900A8C0 0003 0 0 100 00000000 0 00 80 | // eno1 0000A8C0 00000000 0001 0 0 100 00FFFFFF 0 00 81 | const ( 82 | sep = "\t" // field separator 83 | destinationField = 1 // field containing hex destination address 84 | gatewayField = 2 // field containing hex gateway address 85 | maskField = 7 // field containing hex mask 86 | ) 87 | scanner := bufio.NewScanner(bytes.NewReader(output)) 88 | 89 | // Skip header line 90 | if !scanner.Scan() { 91 | return linuxRouteStruct{}, errors.New("Invalid linux route file") 92 | } 93 | 94 | for scanner.Scan() { 95 | row := scanner.Text() 96 | tokens := strings.Split(row, sep) 97 | if len(tokens) < 11 { 98 | return linuxRouteStruct{}, fmt.Errorf("invalid row %q in route file: doesn't have 11 fields", row) 99 | } 100 | 101 | // The default interface is the one that's 0 for both destination and mask. 102 | if !(tokens[destinationField] == "00000000" && tokens[maskField] == "00000000") { 103 | continue 104 | } 105 | 106 | return linuxRouteStruct{ 107 | Iface: tokens[0], 108 | Gateway: tokens[2], 109 | }, nil 110 | } 111 | return linuxRouteStruct{}, errors.New("interface with default destination not found") 112 | } 113 | 114 | func parseWindowsGatewayIP(output []byte) (net.IP, error) { 115 | parsedOutput, err := parseToWindowsRouteStruct(output) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | ip := net.ParseIP(parsedOutput.Gateway) 121 | if ip == nil { 122 | return nil, errCantParse 123 | } 124 | return ip, nil 125 | } 126 | 127 | func parseWindowsInterfaceIP(output []byte) (net.IP, error) { 128 | parsedOutput, err := parseToWindowsRouteStruct(output) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | ip := net.ParseIP(parsedOutput.Interface) 134 | if ip == nil { 135 | return nil, errCantParse 136 | } 137 | return ip, nil 138 | } 139 | 140 | func parseLinuxGatewayIP(output []byte) (net.IP, error) { 141 | parsedStruct, err := parseToLinuxRouteStruct(output) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | // cast hex address to uint32 147 | d, err := strconv.ParseUint(parsedStruct.Gateway, 16, 32) 148 | if err != nil { 149 | return nil, fmt.Errorf( 150 | "parsing default interface address field hex %q: %w", 151 | parsedStruct.Gateway, 152 | err, 153 | ) 154 | } 155 | // make net.IP address from uint32 156 | ipd32 := make(net.IP, 4) 157 | binary.LittleEndian.PutUint32(ipd32, uint32(d)) 158 | return ipd32, nil 159 | } 160 | 161 | func parseLinuxInterfaceIP(output []byte) (net.IP, error) { 162 | parsedStruct, err := parseToLinuxRouteStruct(output) 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | iface, err := net.InterfaceByName(parsedStruct.Iface) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | addrs, err := iface.Addrs() 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | // Return the first IPv4 address we encounter. 178 | for _, addr := range addrs { 179 | ipnet, ok := addr.(*net.IPNet) 180 | if !ok { 181 | continue 182 | } 183 | 184 | ip := ipnet.IP.To4() 185 | if ip != nil { 186 | return ip, nil 187 | } 188 | } 189 | 190 | return nil, fmt.Errorf("no IPv4 address found for interface %v", 191 | parsedStruct.Iface) 192 | } 193 | 194 | func parseDarwinRouteGet(output []byte) (net.IP, error) { 195 | // Darwin route out format is always like this: 196 | // route to: default 197 | // destination: default 198 | // mask: default 199 | // gateway: 192.168.1.1 200 | lines := strings.Split(string(output), "\n") 201 | for _, line := range lines { 202 | fields := strings.Fields(line) 203 | if len(fields) >= 2 && fields[0] == "gateway:" { 204 | ip := net.ParseIP(fields[1]) 205 | if ip != nil { 206 | return ip, nil 207 | } 208 | } 209 | } 210 | 211 | return nil, errNoGateway 212 | } 213 | 214 | func parseDarwinNetstat(output []byte) (net.IP, error) { 215 | // Darwin netstat -nr out format is always like this: 216 | // Routing tables 217 | 218 | // Internet: 219 | // Destination Gateway Flags Netif Expire 220 | // default link#17 UCSg utun3 221 | // default 192.168.1.1 UGScIg en0 222 | outputLines := strings.Split(string(output), "\n") 223 | for _, line := range outputLines { 224 | fields := strings.Fields(line) 225 | 226 | if len(fields) >= 3 && fields[0] == "default" { 227 | // validate routing table flags: 228 | // https://library.netapp.com/ecmdocs/ECMP1155586/html/GUID-07F1F043-7AB7-4749-8F8D-727929233E62.html 229 | // 230 | // U = Up—Route is valid 231 | isUp := strings.Contains(fields[2], "U") 232 | // G = Gateway—Route is to a gateway router rather than to a directly connected network or host 233 | isGateway := strings.Contains(fields[2], "G") 234 | 235 | if isUp && isGateway { 236 | ip := net.ParseIP(fields[1]) 237 | if ip != nil { 238 | return ip, nil 239 | } 240 | } 241 | } 242 | } 243 | return nil, errNoGateway 244 | } 245 | 246 | func parseBSDSolarisNetstat(output []byte) (net.IP, error) { 247 | // netstat -rn produces the following on FreeBSD: 248 | // Routing tables 249 | // 250 | // Internet: 251 | // Destination Gateway Flags Netif Expire 252 | // default 10.88.88.2 UGS em0 253 | // 10.88.88.0/24 link#1 U em0 254 | // 10.88.88.148 link#1 UHS lo0 255 | // 127.0.0.1 link#2 UH lo0 256 | // 257 | // Internet6: 258 | // Destination Gateway Flags Netif Expire 259 | // ::/96 ::1 UGRS lo0 260 | // ::1 link#2 UH lo0 261 | // ::ffff:0.0.0.0/96 ::1 UGRS lo0 262 | // fe80::/10 ::1 UGRS lo0 263 | // ... 264 | outputLines := strings.Split(string(output), "\n") 265 | for _, line := range outputLines { 266 | fields := strings.Fields(line) 267 | if len(fields) >= 2 && fields[0] == "default" { 268 | ip := net.ParseIP(fields[1]) 269 | if ip != nil { 270 | return ip, nil 271 | } 272 | } 273 | } 274 | 275 | return nil, errNoGateway 276 | } 277 | -------------------------------------------------------------------------------- /gateway_test.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | ) 8 | 9 | type testcase struct { 10 | output []byte 11 | ok bool 12 | gateway string 13 | } 14 | 15 | func TestParseWindows(t *testing.T) { 16 | correctData := []byte(` 17 | =========================================================================== 18 | Interface List 19 | 8 ...00 12 3f a7 17 ba ...... Intel(R) PRO/100 VE Network Connection 20 | 1 ........................... Software Loopback Interface 1 21 | =========================================================================== 22 | IPv4 Route Table 23 | =========================================================================== 24 | Active Routes: 25 | Network Destination Netmask Gateway Interface Metric 26 | 0.0.0.0 0.0.0.0 10.88.88.2 10.88.88.149 10 27 | =========================================================================== 28 | Persistent Routes: 29 | `) 30 | localizedData := []byte( 31 | `=========================================================================== 32 | Liste d'Interfaces 33 | 17...00 28 f8 39 61 6b ......Microsoft Wi-Fi Direct Virtual Adapter 34 | 1...........................Software Loopback Interface 1 35 | =========================================================================== 36 | IPv4 Table de routage 37 | =========================================================================== 38 | Itinéraires actifs : 39 | Destination réseau Masque réseau Adr. passerelle Adr. interface Métrique 40 | 0.0.0.0 0.0.0.0 10.88.88.2 10.88.88.149 10 41 | =========================================================================== 42 | Itinéraires persistants : 43 | Aucun 44 | `) 45 | randomData := []byte(` 46 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, 47 | sed do eiusmod tempor incididunt ut labore et dolore magna 48 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation 49 | `) 50 | noRoute := []byte(` 51 | =========================================================================== 52 | Interface List 53 | 8 ...00 12 3f a7 17 ba ...... Intel(R) PRO/100 VE Network Connection 54 | 1 ........................... Software Loopback Interface 1 55 | =========================================================================== 56 | IPv4 Route Table 57 | =========================================================================== 58 | Active Routes: 59 | `) 60 | badRoute1 := []byte(` 61 | =========================================================================== 62 | Interface List 63 | 8 ...00 12 3f a7 17 ba ...... Intel(R) PRO/100 VE Network Connection 64 | 1 ........................... Software Loopback Interface 1 65 | =========================================================================== 66 | IPv4 Route Table 67 | =========================================================================== 68 | Active Routes: 69 | =========================================================================== 70 | Persistent Routes: 71 | `) 72 | badRoute2 := []byte(` 73 | =========================================================================== 74 | Interface List 75 | 8 ...00 12 3f a7 17 ba ...... Intel(R) PRO/100 VE Network Connection 76 | 1 ........................... Software Loopback Interface 1 77 | =========================================================================== 78 | IPv4 Route Table 79 | =========================================================================== 80 | Active Routes: 81 | Network Destination Netmask Gateway Interface Metric 82 | 0.0.0.0 0.0.0.0 foo 10.88.88.149 10 83 | =========================================================================== 84 | Persistent Routes: 85 | `) 86 | 87 | testcases := []testcase{ 88 | {correctData, true, "10.88.88.2"}, 89 | {localizedData, true, "10.88.88.2"}, 90 | {randomData, false, ""}, 91 | {noRoute, false, ""}, 92 | {badRoute1, false, ""}, 93 | {badRoute2, false, ""}, 94 | } 95 | 96 | test(t, testcases, parseWindowsGatewayIP) 97 | 98 | interfaceTestCases := []testcase{ 99 | {correctData, true, "10.88.88.149"}, 100 | {localizedData, true, "10.88.88.149"}, 101 | {randomData, false, ""}, 102 | {noRoute, false, ""}, 103 | {badRoute1, false, ""}, 104 | {badRoute2, true, "10.88.88.149"}, 105 | } 106 | 107 | test(t, interfaceTestCases, parseWindowsInterfaceIP) 108 | } 109 | 110 | func TestParseLinux(t *testing.T) { 111 | correctData := []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT 112 | wlp4s0 0000FEA9 00000000 0001 0 0 1000 0000FFFF 0 0 0 113 | docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 114 | docker_gwbridge 000012AC 00000000 0001 0 0 0 0000FFFF 0 0 0 115 | wlp4s0 0008A8C0 00000000 0001 0 0 600 00FFFFFF 0 0 0 116 | wlp4s0 00000000 0108A8C0 0003 0 0 600 00000000 0 0 0 117 | `) 118 | noRoute := []byte(` 119 | Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT 120 | `) 121 | 122 | testcases := []testcase{ 123 | {correctData, true, "192.168.8.1"}, 124 | {noRoute, false, ""}, 125 | } 126 | 127 | test(t, testcases, parseLinuxGatewayIP) 128 | 129 | // ifData := []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT 130 | // eth0 00000000 00000000 0001 0 0 1000 0000FFFF 0 0 0 131 | // `) 132 | // interfaceTestCases := []testcase{ 133 | // {ifData, true, "192.168.8.238"}, 134 | // {noRoute, false, ""}, 135 | // } 136 | 137 | // to run interface test in your local computer, change eth0 with your default interface name, and change the expected IP to be your default IP 138 | // test(t, interfaceTestCases, parseLinuxInterfaceIP) 139 | } 140 | 141 | func TestParseBSDSolarisNetstat(t *testing.T) { 142 | correctDataFreeBSD := []byte(` 143 | Routing tables 144 | 145 | Internet: 146 | Destination Gateway Flags Netif Expire 147 | default 10.88.88.2 UGS em0 148 | 10.88.88.0/24 link#1 U em0 149 | 10.88.88.148 link#1 UHS lo0 150 | 127.0.0.1 link#2 UH lo0 151 | 152 | Internet6: 153 | Destination Gateway Flags Netif Expire 154 | ::/96 ::1 UGRS lo0 155 | ::1 link#2 UH lo0 156 | ::ffff:0.0.0.0/96 ::1 UGRS lo0 157 | fe80::/10 ::1 UGRS lo0 158 | `) 159 | correctDataSolaris := []byte(` 160 | Routing Table: IPv4 161 | Destination Gateway Flags Ref Use Interface 162 | -------------------- -------------------- ----- ----- ---------- --------- 163 | default 172.16.32.1 UG 2 76419 net0 164 | 127.0.0.1 127.0.0.1 UH 2 36 lo0 165 | 172.16.32.0 172.16.32.17 U 4 8100 net0 166 | 167 | Routing Table: IPv6 168 | Destination/Mask Gateway Flags Ref Use If 169 | --------------------------- --------------------------- ----- --- ------- ----- 170 | ::1 ::1 UH 3 75382 lo0 171 | 2001:470:deeb:32::/64 2001:470:deeb:32::17 U 3 2744 net0 172 | fe80::/10 fe80::6082:52ff:fedc:7df0 U 3 8430 net0 173 | `) 174 | randomData := []byte(` 175 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, 176 | sed do eiusmod tempor incididunt ut labore et dolore magna 177 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation 178 | `) 179 | noRoute := []byte(` 180 | Internet: 181 | Destination Gateway Flags Netif Expire 182 | 10.88.88.0/24 link#1 U em0 183 | 10.88.88.148 link#1 UHS lo0 184 | 127.0.0.1 link#2 UH lo0 185 | `) 186 | badRoute := []byte(` 187 | Internet: 188 | Destination Gateway Flags Netif Expire 189 | default foo UGS em0 190 | 10.88.88.0/24 link#1 U em0 191 | 10.88.88.148 link#1 UHS lo0 192 | 127.0.0.1 link#2 UH lo0 193 | `) 194 | 195 | testcases := []testcase{ 196 | {correctDataFreeBSD, true, "10.88.88.2"}, 197 | {correctDataSolaris, true, "172.16.32.1"}, 198 | {randomData, false, ""}, 199 | {noRoute, false, ""}, 200 | {badRoute, false, ""}, 201 | } 202 | 203 | test(t, testcases, parseBSDSolarisNetstat) 204 | } 205 | 206 | func TestParseDarwinRouteGet(t *testing.T) { 207 | correctData := []byte(` 208 | route to: 0.0.0.0 209 | destination: default 210 | mask: default 211 | gateway: 172.16.32.1 212 | interface: en0 213 | flags: 214 | recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire 215 | 0 0 0 0 0 0 1500 0 216 | `) 217 | randomData := []byte(` 218 | test 219 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, 220 | sed do eiusmod tempor incididunt ut labore et dolore magna 221 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation 222 | `) 223 | noRoute := []byte(` 224 | route to: 0.0.0.0 225 | destination: default 226 | mask: default 227 | `) 228 | badRoute := []byte(` 229 | route to: 0.0.0.0 230 | destination: default 231 | mask: default 232 | gateway: foo 233 | interface: en0 234 | flags: 235 | recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire 236 | 0 0 0 0 0 0 1500 0 237 | `) 238 | 239 | testcases := []testcase{ 240 | {correctData, true, "172.16.32.1"}, 241 | {randomData, false, ""}, 242 | {noRoute, false, ""}, 243 | {badRoute, false, ""}, 244 | } 245 | 246 | test(t, testcases, parseDarwinRouteGet) 247 | } 248 | 249 | func TestParseDarwinNetstat(t *testing.T) { 250 | correctDataDarwin := []byte(` 251 | Internet: 252 | Destination Gateway Flags Netif Expire 253 | default link#17 UCSg utun3 254 | default 192.168.1.254 UGScIg en0 255 | `) 256 | randomData := []byte(` 257 | test 258 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, 259 | sed do eiusmod tempor incididunt ut labore et dolore magna 260 | aliqua. Ut enim ad minim veniam, quis nostrud exercitation 261 | `) 262 | 263 | noRoute := []byte(` 264 | Internet: 265 | Destination Gateway Flags Netif Expire 266 | 10.88.88.0/24 link#1 U em0 267 | 10.88.88.148 link#1 UHS lo0 268 | 127.0.0.1 link#2 UH lo0 269 | `) 270 | 271 | badRoute := []byte(` 272 | Internet: 273 | Destination Gateway Flags Netif Expire 274 | default foo UGS em0 275 | 10.88.88.0/24 link#1 U em0 276 | 10.88.88.148 link#1 UHS lo0 277 | 127.0.0.1 link#2 UH lo0 278 | `) 279 | 280 | testcases := []testcase{ 281 | {correctDataDarwin, true, "192.168.1.254"}, 282 | {randomData, false, ""}, 283 | {noRoute, false, ""}, 284 | {badRoute, false, ""}, 285 | } 286 | 287 | test(t, testcases, parseDarwinNetstat) 288 | } 289 | 290 | func test(t *testing.T, testcases []testcase, fn func([]byte) (net.IP, error)) { 291 | for i, tc := range testcases { 292 | net, err := fn(tc.output) 293 | if tc.ok { 294 | if err != nil { 295 | t.Errorf("Unexpected error in test #%d: %v", i, err) 296 | } 297 | if net.String() != tc.gateway { 298 | t.Errorf("Unexpected gateway address %v != %s", net, tc.gateway) 299 | } 300 | } else if err == nil { 301 | t.Errorf("Unexpected nil error in test #%d", i) 302 | } 303 | } 304 | } 305 | 306 | func ExampleDiscoverGateway() { 307 | gateway, err := DiscoverGateway() 308 | if err != nil { 309 | fmt.Println(err) 310 | } else { 311 | fmt.Println("Gateway:", gateway.String()) 312 | } 313 | } 314 | --------------------------------------------------------------------------------