From a7a11951505e704726233059f492db249cbe2966 Mon Sep 17 00:00:00 2001 From: crusader Date: Wed, 22 Aug 2018 14:50:46 +0900 Subject: [PATCH] project initialized --- .gitignore | 69 ++++++++ .vscode/launch.json | 32 ++++ .vscode/settings.json | 14 ++ Gopkg.toml | 30 ++++ net/gateway/gateway-common.go | 162 +++++++++++++++++ net/gateway/gateway_darwin.go | 16 ++ net/gateway/gateway_freebsd.go | 16 ++ net/gateway/gateway_linux.go | 34 ++++ net/gateway/gateway_solaris.go | 16 ++ net/gateway/gateway_test.go | 248 +++++++++++++++++++++++++++ net/gateway/gateway_unimplemented.go | 14 ++ net/gateway/gateway_windows.go | 16 ++ 12 files changed, 667 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 Gopkg.toml create mode 100644 net/gateway/gateway-common.go create mode 100644 net/gateway/gateway_darwin.go create mode 100644 net/gateway/gateway_freebsd.go create mode 100644 net/gateway/gateway_linux.go create mode 100644 net/gateway/gateway_solaris.go create mode 100644 net/gateway/gateway_test.go create mode 100644 net/gateway/gateway_unimplemented.go create mode 100644 net/gateway/gateway_windows.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f088aff --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ +.idea/ +*.iml + +vendor/ +glide.lock +.DS_Store +dist/ +debug +Gopkg.lock \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..314c5af --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}/main.go", + "env": {}, + "args": [], + "showLog": true + }, + { + "name": "File Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [], + "showLog": true + } + + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..71eb7e3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.autoClosingBrackets": true, + "editor.trimAutoWhitespace": true, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "go.testFlags": [ + "-v", + ], + "go.testTimeout": "300s" +} \ No newline at end of file diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..24f0f17 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,30 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[prune] + go-tests = true + unused-packages = true diff --git a/net/gateway/gateway-common.go b/net/gateway/gateway-common.go new file mode 100644 index 0000000..532ee32 --- /dev/null +++ b/net/gateway/gateway-common.go @@ -0,0 +1,162 @@ +package gateway + +import ( + "errors" + "net" + "strings" +) + +var errNoGateway = errors.New("no gateway found") + +func parseWindowsRoutePrint(output []byte) (net.IP, string, error) { + // Windows route output format is always like this: + // =========================================================================== + // 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 + // =========================================================================== + // I'm trying to pick the active route, + // then jump 2 lines and pick the third IP + // Not using regex because output is quite standard from Windows XP to 8 (NEEDS TESTING) + lines := strings.Split(string(output), "\n") + for idx, line := range lines { + if strings.HasPrefix(line, "Active Routes:") { + if len(lines) <= idx+2 { + return nil, "", errNoGateway + } + + fields := strings.Fields(lines[idx+2]) + if len(fields) < 3 { + return nil, "", errNoGateway + } + + ip := net.ParseIP(fields[2]) + if ip != nil { + return ip, fields[3], nil + } + } + } + return nil, "", errNoGateway +} + +func parseLinuxIPRoute(output []byte) (net.IP, string, error) { + // Linux '/usr/bin/ip route show' format looks like this: + // default via 192.168.178.1 dev wlp3s0 metric 303 + // 192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 + lines := strings.Split(string(output), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 3 && fields[0] == "default" { + ip := net.ParseIP(fields[2]) + if ip != nil { + return ip, fields[4], nil + } + } + } + + return nil, "", errNoGateway +} + +func parseLinuxRoute(output []byte) (net.IP, string, error) { + // Linux route out format is always like this: + // Kernel IP routing table + // Destination Gateway Genmask Flags Metric Ref Use Iface + // 0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0 + lines := strings.Split(string(output), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 2 && fields[0] == "0.0.0.0" { + ip := net.ParseIP(fields[1]) + if ip != nil { + return ip, fields[7], nil + } + } + } + + return nil, "", errNoGateway +} + +func parseDarwinRouteGet(output []byte) (net.IP, string, error) { + // Darwin route out format is always like this: + // route to: default + // destination: default + // mask: default + // gateway: 192.168.1.1 + // interface: tun0 + // flags: + // lines := strings.Split(string(output), "\n") + // for _, line := range lines { + // fields := strings.Fields(line) + // if len(fields) >= 2 && fields[0] == "gateway:" { + // ip := net.ParseIP(fields[1]) + // if ip != nil { + // return ip, "", nil + // } + // } + // } + + // Darwin route out format is always like this: + // Internet: + // Destination Gateway Flags Refs Use Netif Expire + // default 192.168.10.254 UGSc 194 0 en3 + // 127 127.0.0.1 UCS 0 429 lo0 + // 127.0.0.1 127.0.0.1 UH 1 587632 lo0 + // 169.254 link#7 UCS 0 0 en3 + // 192.168.10 link#7 UCS 4 0 en3 + // 192.168.10.1 0:11:32:7f:20:61 UHLWIi 1 202 en3 1065 + // 224.0.0/4 link#7 UmCS 3 0 en3 + // 224.0.0.251 1:0:5e:0:0:fb UHmLWI 0 2325 en3 + // 239.192.152.143 1:0:5e:40:98:8f UHmLWI 0 22892 en3 + // 239.255.255.250 1:0:5e:7f:ff:fa UHmLWI 0 15988 en3 + // 255.255.255.255/32 link#7 UCS 0 0 en3 + + // Internet6: + // Destination Gateway Flags Netif Expire + // default fe80::%utun0 UGcI utun0 + // default fe80::%utun1 UGcI utun1 + // default fe80::%utun2 UGcI utun2 + // default fe80::%utun3 UGcI utun3 + outputLines := strings.Split(string(output), "\n") + for _, line := range outputLines { + fields := strings.Fields(line) + if len(fields) >= 2 && fields[0] == "default" { + ip := net.ParseIP(fields[1]) + if ip != nil { + return ip, fields[5], nil + } + } + } + return nil, "", errNoGateway +} + +func parseBSDSolarisNetstat(output []byte) (net.IP, string, error) { + // netstat -rn produces the following on FreeBSD: + // Routing tables + // + // Internet: + // Destination Gateway Flags Netif Expire + // default 10.88.88.2 UGS em0 + // 10.88.88.0/24 link#1 U em0 + // 10.88.88.148 link#1 UHS lo0 + // 127.0.0.1 link#2 UH lo0 + // + // Internet6: + // Destination Gateway Flags Netif Expire + // ::/96 ::1 UGRS lo0 + // ::1 link#2 UH lo0 + // ::ffff:0.0.0.0/96 ::1 UGRS lo0 + // fe80::/10 ::1 UGRS lo0 + // ... + outputLines := strings.Split(string(output), "\n") + for _, line := range outputLines { + fields := strings.Fields(line) + if len(fields) >= 2 && fields[0] == "default" { + ip := net.ParseIP(fields[1]) + if ip != nil { + return ip, fields[3], nil + } + } + } + + return nil, "", errNoGateway +} diff --git a/net/gateway/gateway_darwin.go b/net/gateway/gateway_darwin.go new file mode 100644 index 0000000..aad613d --- /dev/null +++ b/net/gateway/gateway_darwin.go @@ -0,0 +1,16 @@ +package gateway + +import ( + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, iface string, err error) { + routeCmd := exec.Command("netstat", "-rn") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, "", err + } + + return parseDarwinRouteGet(output) +} diff --git a/net/gateway/gateway_freebsd.go b/net/gateway/gateway_freebsd.go new file mode 100644 index 0000000..035c22e --- /dev/null +++ b/net/gateway/gateway_freebsd.go @@ -0,0 +1,16 @@ +package gateway + +import ( + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, iface string, err error) { + routeCmd := exec.Command("netstat", "-rn") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, "", err + } + + return parseBSDSolarisNetstat(output) +} diff --git a/net/gateway/gateway_linux.go b/net/gateway/gateway_linux.go new file mode 100644 index 0000000..daec1c7 --- /dev/null +++ b/net/gateway/gateway_linux.go @@ -0,0 +1,34 @@ +package gateway + +import ( + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, iface string, err error) { + ip, iface, err = discoverGatewayUsingRoute() + if err != nil { + ip, iface, err = discoverGatewayUsingIp() + } + return +} + +func discoverGatewayUsingIp() (net.IP, string, error) { + routeCmd := exec.Command("ip", "route", "show") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, "", err + } + + return parseLinuxIPRoute(output) +} + +func discoverGatewayUsingRoute() (net.IP, string, error) { + routeCmd := exec.Command("route", "-n") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, "", err + } + + return parseLinuxRoute(output) +} diff --git a/net/gateway/gateway_solaris.go b/net/gateway/gateway_solaris.go new file mode 100644 index 0000000..035c22e --- /dev/null +++ b/net/gateway/gateway_solaris.go @@ -0,0 +1,16 @@ +package gateway + +import ( + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, iface string, err error) { + routeCmd := exec.Command("netstat", "-rn") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, "", err + } + + return parseBSDSolarisNetstat(output) +} diff --git a/net/gateway/gateway_test.go b/net/gateway/gateway_test.go new file mode 100644 index 0000000..e519173 --- /dev/null +++ b/net/gateway/gateway_test.go @@ -0,0 +1,248 @@ +package gateway + +import ( + "net" + "testing" +) + +type testcase struct { + output []byte + ok bool + gateway string +} + +func TestParseWindowsRoutePrint(t *testing.T) { + correctData := []byte(` +IPv4 Route Table +=========================================================================== +Active Routes: +Network Destination Netmask Gateway Interface Metric + 0.0.0.0 0.0.0.0 10.88.88.2 10.88.88.149 10 +=========================================================================== +Persistent Routes: +`) + randomData := []byte(` +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation +`) + noRoute := []byte(` +IPv4 Route Table +=========================================================================== +Active Routes: +`) + badRoute1 := []byte(` +IPv4 Route Table +=========================================================================== +Active Routes: +=========================================================================== +Persistent Routes: +`) + badRoute2 := []byte(` +IPv4 Route Table +=========================================================================== +Active Routes: +Network Destination Netmask Gateway Interface Metric + 0.0.0.0 0.0.0.0 foo 10.88.88.149 10 +=========================================================================== +Persistent Routes: +`) + + testcases := []testcase{ + {correctData, true, "10.88.88.2"}, + {randomData, false, ""}, + {noRoute, false, ""}, + {badRoute1, false, ""}, + {badRoute2, false, ""}, + } + + test(t, testcases, parseWindowsRoutePrint) +} + +func TestParseLinuxIPRoutePrint(t *testing.T) { + correctData := []byte(` +default via 192.168.178.1 dev wlp3s0 metric 303 +192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 +`) + randomData := []byte(` +test +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation +`) + noRoute := []byte(` +192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 +`) + badRoute := []byte(` +default via foo dev wlp3s0 metric 303 +192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 +`) + + testcases := []testcase{ + {correctData, true, "192.168.178.1"}, + {randomData, false, ""}, + {noRoute, false, ""}, + {badRoute, false, ""}, + } + + test(t, testcases, parseLinuxIPRoute) +} + +func TestParseLinuxRoutePrint(t *testing.T) { + correctData := []byte(` +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0 +`) + randomData := []byte(` +test +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation +`) + noRoute := []byte(` +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +`) + badRoute := []byte(` +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +0.0.0.0 foo 0.0.0.0 UG 0 0 0 eth0 +`) + + testcases := []testcase{ + {correctData, true, "192.168.1.1"}, + {randomData, false, ""}, + {noRoute, false, ""}, + {badRoute, false, ""}, + } + + test(t, testcases, parseLinuxRoute) +} + +func TestParseDarwinRouteGet(t *testing.T) { + correctData := []byte(` + route to: 0.0.0.0 +destination: default + mask: default + gateway: 172.16.32.1 + interface: en0 + flags: + recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire + 0 0 0 0 0 0 1500 0 +`) + randomData := []byte(` +test +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation +`) + noRoute := []byte(` + route to: 0.0.0.0 +destination: default + mask: default +`) + badRoute := []byte(` + route to: 0.0.0.0 +destination: default + mask: default + gateway: foo + interface: en0 + flags: + recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire + 0 0 0 0 0 0 1500 0 +`) + + testcases := []testcase{ + {correctData, true, "172.16.32.1"}, + {randomData, false, ""}, + {noRoute, false, ""}, + {badRoute, false, ""}, + } + + test(t, testcases, parseDarwinRouteGet) +} + +func TestParseBSDSolarisNetstat(t *testing.T) { + correctDataFreeBSD := []byte(` +Routing tables + +Internet: +Destination Gateway Flags Netif Expire +default 10.88.88.2 UGS em0 +10.88.88.0/24 link#1 U em0 +10.88.88.148 link#1 UHS lo0 +127.0.0.1 link#2 UH lo0 + +Internet6: +Destination Gateway Flags Netif Expire +::/96 ::1 UGRS lo0 +::1 link#2 UH lo0 +::ffff:0.0.0.0/96 ::1 UGRS lo0 +fe80::/10 ::1 UGRS lo0 +`) + correctDataSolaris := []byte(` +Routing Table: IPv4 + Destination Gateway Flags Ref Use Interface +-------------------- -------------------- ----- ----- ---------- --------- +default 172.16.32.1 UG 2 76419 net0 +127.0.0.1 127.0.0.1 UH 2 36 lo0 +172.16.32.0 172.16.32.17 U 4 8100 net0 + +Routing Table: IPv6 + Destination/Mask Gateway Flags Ref Use If +--------------------------- --------------------------- ----- --- ------- ----- +::1 ::1 UH 3 75382 lo0 +2001:470:deeb:32::/64 2001:470:deeb:32::17 U 3 2744 net0 +fe80::/10 fe80::6082:52ff:fedc:7df0 U 3 8430 net0 +`) + randomData := []byte(` +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation +`) + noRoute := []byte(` +Internet: +Destination Gateway Flags Netif Expire +10.88.88.0/24 link#1 U em0 +10.88.88.148 link#1 UHS lo0 +127.0.0.1 link#2 UH lo0 +`) + badRoute := []byte(` +Internet: +Destination Gateway Flags Netif Expire +default foo UGS em0 +10.88.88.0/24 link#1 U em0 +10.88.88.148 link#1 UHS lo0 +127.0.0.1 link#2 UH lo0 +`) + + testcases := []testcase{ + {correctDataFreeBSD, true, "10.88.88.2"}, + {correctDataSolaris, true, "172.16.32.1"}, + {randomData, false, ""}, + {noRoute, false, ""}, + {badRoute, false, ""}, + } + + test(t, testcases, parseBSDSolarisNetstat) +} + +func test(t *testing.T, testcases []testcase, fn func([]byte) (net.IP, string, error)) { + for i, tc := range testcases { + net, iface, err := fn(tc.output) + if tc.ok { + if err != nil { + t.Errorf("Unexpected error in test #%d: %v", i, err) + } + if net.String() != tc.gateway { + t.Errorf("Unexpected gateway address %v != %s", net, tc.gateway) + } + if "" == iface { + t.Errorf("Unexpected interface") + } + } else if err == nil { + t.Errorf("Unexpected nil error in test #%d", i) + } + } +} diff --git a/net/gateway/gateway_unimplemented.go b/net/gateway/gateway_unimplemented.go new file mode 100644 index 0000000..676553e --- /dev/null +++ b/net/gateway/gateway_unimplemented.go @@ -0,0 +1,14 @@ +// +build !darwin,!linux,!windows,!solaris,!freebsd + +package gateway + +import ( + "fmt" + "net" + "runtime" +) + +func DiscoverGateway() (ip net.IP, iface string, err error) { + err = fmt.Errorf("DiscoverGateway not implemented for OS %s", runtime.GOOS) + return +} diff --git a/net/gateway/gateway_windows.go b/net/gateway/gateway_windows.go new file mode 100644 index 0000000..685f2c2 --- /dev/null +++ b/net/gateway/gateway_windows.go @@ -0,0 +1,16 @@ +package gateway + +import ( + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, iface string, err error) { + routeCmd := exec.Command("route", "print", "0.0.0.0") + output, err := routeCmd.CombinedOutput() + if err != nil { + return nil, "", err + } + + return parseWindowsRoutePrint(output) +}