diff --git a/Makefile b/GNUmakefile similarity index 63% rename from Makefile rename to GNUmakefile index 224135dc1..f3dfd24cf 100644 --- a/Makefile +++ b/GNUmakefile @@ -2,6 +2,8 @@ TOOLS= golang.org/x/tools/cover GOCOVER_TMPFILE?= $(GOCOVER_FILE).tmp GOCOVER_FILE?= .cover.out GOCOVERHTML?= coverage.html +FIND=`/usr/bin/which 2> /dev/null gfind find | /usr/bin/grep -v ^no | /usr/bin/head -n 1` +XARGS=`/usr/bin/which 2> /dev/null gxargs xargs | /usr/bin/grep -v ^no | /usr/bin/head -n 1` test:: $(GOCOVER_FILE) @$(MAKE) -C cmd/sockaddr test @@ -9,10 +11,10 @@ test:: $(GOCOVER_FILE) cover:: coverage_report $(GOCOVER_FILE):: - @find . -type d ! -path '*cmd*' ! -path '*.git*' -print0 | xargs -0 -I % sh -ec "cd % && rm -f $(GOCOVER_TMPFILE) && go test -coverprofile=$(GOCOVER_TMPFILE)" + @${FIND} . -type d ! -path '*cmd*' ! -path '*.git*' -print0 | ${XARGS} -0 -I % sh -ec "cd % && rm -f $(GOCOVER_TMPFILE) && go test -coverprofile=$(GOCOVER_TMPFILE)" @echo 'mode: set' > $(GOCOVER_FILE) - @find . -type f ! -path '*cmd*' ! -path '*.git*' -name "$(GOCOVER_TMPFILE)" -print0 | xargs -0 -n1 cat $(GOCOVER_TMPFILE) | grep -v '^mode: ' >> ${PWD}/$(GOCOVER_FILE) + @${FIND} . -type f ! -path '*cmd*' ! -path '*.git*' -name "$(GOCOVER_TMPFILE)" -print0 | ${XARGS} -0 -n1 cat $(GOCOVER_TMPFILE) | grep -v '^mode: ' >> ${PWD}/$(GOCOVER_FILE) $(GOCOVERHTML): $(GOCOVER_FILE) go tool cover -html=$(GOCOVER_FILE) -o $(GOCOVERHTML) @@ -41,15 +43,15 @@ clean:: dev:: @go build - @make -B -C cmd/sockaddr sockaddr + @$(MAKE) -B -C cmd/sockaddr sockaddr install:: @go install - @make -C cmd/sockaddr install + @$(MAKE) -C cmd/sockaddr install doc:: - echo Visit: http://127.0.0.1:6060/pkg/github.com/hashicorp/go-sockaddr/ - godoc -http=:6060 -goroot $GOROOT + @echo Visit: http://127.0.0.1:6161/pkg/github.com/hashicorp/go-sockaddr/ + godoc -http=:6161 -goroot $GOROOT world:: @set -e; \ @@ -60,4 +62,4 @@ world:: done; \ done - make -C cmd/sockaddr world + $(MAKE) -C cmd/sockaddr world diff --git a/cmd/sockaddr/GNUmakefile b/cmd/sockaddr/GNUmakefile index 90232d2f0..6d0039ae5 100644 --- a/cmd/sockaddr/GNUmakefile +++ b/cmd/sockaddr/GNUmakefile @@ -21,7 +21,7 @@ install:: $(BIN) .PHONY: test test:: $(BIN) - @make -C regression + @$(MAKE) -C regression .PHONY: world world:: diff --git a/ifaddr.go b/ifaddr.go index 3e4ff9fca..0811b2759 100644 --- a/ifaddr.go +++ b/ifaddr.go @@ -1,5 +1,7 @@ package sockaddr +import "strings" + // ifAddrAttrMap is a map of the IfAddr type-specific attributes. var ifAddrAttrMap map[AttrName]func(IfAddr) string var ifAddrAttrs []AttrName @@ -30,6 +32,53 @@ func GetPrivateIP() (string, error) { return ip.NetIP().String(), nil } +// GetPrivateIPs returns a string with all IP addresses that are part of RFC +// 6890 (regardless of whether or not there is a default route, unlike +// GetPublicIP). If the system can't find any RFC 6890 IP addresses, an empty +// string will be returned instead. This function is the `eval` equivalent of: +// +// ``` +// $ sockaddr eval -r '{{GetAllInterfaces | include "RFC" "6890" | join "address" " "}}' +/// ``` +func GetPrivateIPs() (string, error) { + ifAddrs, err := GetAllInterfaces() + if err != nil { + return "", err + } else if len(ifAddrs) < 1 { + return "", nil + } + + ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP) + if len(ifAddrs) == 0 { + return "", nil + } + + OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs) + + ifAddrs, _, err = IfByRFC("6890", ifAddrs) + if err != nil { + return "", err + } else if len(ifAddrs) == 0 { + return "", nil + } + + _, ifAddrs, err = IfByRFC(ForwardingBlacklistRFC, ifAddrs) + if err != nil { + return "", err + } else if len(ifAddrs) == 0 { + return "", nil + } + + ips := make([]string, 0, len(ifAddrs)) + for _, ifAddr := range ifAddrs { + ip := *ToIPAddr(ifAddr.SockAddr) + s := ip.NetIP().String() + ips = append(ips, s) + } + + return strings.Join(ips, " "), nil +} + // GetPublicIP returns a string with a single IP address that is NOT part of RFC // 6890 and has a default route. If the system can't determine its IP address // or find a non RFC 6890 IP address, an empty string will be returned instead. @@ -51,6 +100,47 @@ func GetPublicIP() (string, error) { return ip.NetIP().String(), nil } +// GetPublicIPs returns a string with all IP addresses that are NOT part of RFC +// 6890 (regardless of whether or not there is a default route, unlike +// GetPublicIP). If the system can't find any non RFC 6890 IP addresses, an +// empty string will be returned instead. This function is the `eval` +// equivalent of: +// +// ``` +// $ sockaddr eval -r '{{GetAllInterfaces | exclude "RFC" "6890" | join "address" " "}}' +/// ``` +func GetPublicIPs() (string, error) { + ifAddrs, err := GetAllInterfaces() + if err != nil { + return "", err + } else if len(ifAddrs) < 1 { + return "", nil + } + + ifAddrs, _ = FilterIfByType(ifAddrs, TypeIP) + if len(ifAddrs) == 0 { + return "", nil + } + + OrderedIfAddrBy(AscIfType, AscIfNetworkSize).Sort(ifAddrs) + + _, ifAddrs, err = IfByRFC("6890", ifAddrs) + if err != nil { + return "", err + } else if len(ifAddrs) == 0 { + return "", nil + } + + ips := make([]string, 0, len(ifAddrs)) + for _, ifAddr := range ifAddrs { + ip := *ToIPAddr(ifAddr.SockAddr) + s := ip.NetIP().String() + ips = append(ips, s) + } + + return strings.Join(ips, " "), nil +} + // GetInterfaceIP returns a string with a single IP address sorted by the size // of the network (i.e. IP addresses with a smaller netmask, larger network // size, are sorted first). This function is the `eval` equivalent of: @@ -91,6 +181,44 @@ func GetInterfaceIP(namedIfRE string) (string, error) { return IPAddrAttr(*ip, "address"), nil } +// GetInterfaceIPs returns a string with all IPs, sorted by the size of the +// network (i.e. IP addresses with a smaller netmask, larger network size, are +// sorted first), on a named interface. This function is the `eval` equivalent +// of: +// +// ``` +// $ sockaddr eval -r '{{GetAllInterfaces | include "name" <> | sort "type,size" | join "address" " "}}' +/// ``` +func GetInterfaceIPs(namedIfRE string) (string, error) { + ifAddrs, err := GetAllInterfaces() + if err != nil { + return "", err + } + + ifAddrs, _, err = IfByName(namedIfRE, ifAddrs) + if err != nil { + return "", err + } + + ifAddrs, err = SortIfBy("+type,+size", ifAddrs) + if err != nil { + return "", err + } + + if len(ifAddrs) == 0 { + return "", err + } + + ips := make([]string, 0, len(ifAddrs)) + for _, ifAddr := range ifAddrs { + ip := *ToIPAddr(ifAddr.SockAddr) + s := ip.NetIP().String() + ips = append(ips, s) + } + + return strings.Join(ips, " "), nil +} + // IfAddrAttrs returns a list of attributes supported by the IfAddr type func IfAddrAttrs() []AttrName { return ifAddrAttrs diff --git a/ifaddr_test.go b/ifaddr_test.go index e3b3e93ea..859c3e421 100644 --- a/ifaddr_test.go +++ b/ifaddr_test.go @@ -45,6 +45,42 @@ func havePublicIP() bool { return boolEnvVar("SOCKADDR_HAVE_PUBLIC_IP", false) } +func TestGetPrivateIP(t *testing.T) { + reportOnPrivate := func(args ...interface{}) { + if havePrivateIP() { + t.Fatalf(args[0].(string), args[1:]...) + } else { + t.Skipf(args[0].(string), args[1:]...) + } + } + ip, err := sockaddr.GetPrivateIP() + if err != nil { + reportOnPrivate("unable to get a private IP: %v", err) + } + + if ip == "" { + reportOnPrivate("it's hard to test this reliably") + } +} + +func TestGetPrivateIPs(t *testing.T) { + reportOnPrivate := func(args ...interface{}) { + if havePrivateIP() { + t.Fatalf(args[0].(string), args[1:]...) + } else { + t.Skipf(args[0].(string), args[1:]...) + } + } + ips, err := sockaddr.GetPrivateIPs() + if err != nil { + reportOnPrivate("unable to get a private IPs: %v", err) + } + + if ips == "" { + reportOnPrivate("it's hard to test this reliably") + } +} + func TestGetPublicIP(t *testing.T) { reportOnPublic := func(args ...interface{}) { if havePublicIP() { @@ -63,6 +99,24 @@ func TestGetPublicIP(t *testing.T) { } } +func TestGetPublicIPs(t *testing.T) { + reportOnPublic := func(args ...interface{}) { + if havePublicIP() { + t.Fatalf(args[0].(string), args[1:]...) + } else { + t.Skipf(args[0].(string), args[1:]...) + } + } + ips, err := sockaddr.GetPublicIPs() + if err != nil { + reportOnPublic("unable to get a public IPs: %v", err) + } + + if ips == "" { + reportOnPublic("it's hard to test this reliably") + } +} + func TestGetInterfaceIP(t *testing.T) { ip, err := sockaddr.GetInterfaceIP(`^.*[\d]$`) if err != nil { diff --git a/ifaddrs_test.go b/ifaddrs_test.go index a055042f6..aed847808 100644 --- a/ifaddrs_test.go +++ b/ifaddrs_test.go @@ -697,25 +697,6 @@ func TestGetDefaultInterface(t *testing.T) { } } -func TestGetPrivateIP(t *testing.T) { - reportOnPrivate := func(args ...interface{}) { - if havePrivateIP() { - t.Fatalf(args[0].(string), args[1:]...) - } else { - t.Skipf(args[0].(string), args[1:]...) - } - } - - ip, err := sockaddr.GetPrivateIP() - if err != nil { - reportOnPrivate("private IP failed: %v", err) - } - - if len(ip) == 0 { - reportOnPrivate("no private IP found", nil) - } -} - func TestIfAddrAttrs(t *testing.T) { const expectedNumAttrs = 2 attrs := sockaddr.IfAddrAttrs() @@ -838,7 +819,7 @@ func TestGetPrivateInterfaces(t *testing.T) { } if len(ifAddrs) == 0 { - reportOnPrivate("no public IPs found", nil) + reportOnPrivate("no public IPs found") } if len(ifAddrs[0].String()) == 0 { diff --git a/rfc.go b/rfc.go index fd9be940b..02e188f6f 100644 --- a/rfc.go +++ b/rfc.go @@ -3,6 +3,7 @@ package sockaddr // ForwardingBlacklist is a faux RFC that includes a list of non-forwardable IP // blocks. const ForwardingBlacklist = 4294967295 +const ForwardingBlacklistRFC = "4294967295" // IsRFC tests to see if an SockAddr matches the specified RFC func IsRFC(rfcNum uint, sa SockAddr) bool { diff --git a/template/Makefile b/template/GNUmakefile similarity index 100% rename from template/Makefile rename to template/GNUmakefile diff --git a/template/doc.go b/template/doc.go index 9f4ff8e40..90c8784a3 100644 --- a/template/doc.go +++ b/template/doc.go @@ -79,6 +79,14 @@ Example: {{ GetPrivateIP }} +`GetPrivateIPs` - Helper function that returns a string of the all private IP +addresses on the host. + +Example: + + {{ GetPrivateIPs }} + + `GetPublicIP` - Helper function that returns a string of the first IP from GetPublicInterfaces. @@ -86,12 +94,29 @@ Example: {{ GetPublicIP }} +`GetPublicIPs` - Helper function that returns a space-delimited string of the +all public IP addresses on the host. + +Example: + + {{ GetPrivateIPs }} + + `GetInterfaceIP` - Helper function that returns a string of the first IP from the named interface. Example: - {{ GetInterfaceIP "eth1" }} + {{ GetInterfaceIP "en0" }} + + + +`GetInterfaceIPs` - Helper function that returns a space-delimited list of all +IPs on a given interface. + +Example: + + {{ GetInterfaceIPs "en0" }} `sort` - Sorts the IfAddrs result based on its arguments. `sort` takes one diff --git a/template/template.go b/template/template.go index dfb9a5c56..bbed51361 100644 --- a/template/template.go +++ b/template/template.go @@ -77,13 +77,27 @@ func init() { // to the default route and a forwardable address. "GetPrivateIP": sockaddr.GetPrivateIP, + // Return all Private RFC 6890 IP addresses as a space-delimited string of + // IP addresses. Addresses returned do not have to be on the interface with + // a default route. + "GetPrivateIPs": sockaddr.GetPrivateIPs, + // Return a Public RFC 6890 IP address string that is attached // to the default route and a forwardable address. "GetPublicIP": sockaddr.GetPublicIP, + // Return allPublic RFC 6890 IP addresses as a space-delimited string of IP + // addresses. Addresses returned do not have to be on the interface with a + // default route. + "GetPublicIPs": sockaddr.GetPublicIPs, + // Return the first IP address of the named interface, sorted by // the largest network size. "GetInterfaceIP": sockaddr.GetInterfaceIP, + + // Return all IP addresses on the named interface, sorted by the largest + // network size. + "GetInterfaceIPs": sockaddr.GetInterfaceIPs, } } diff --git a/template/template_test.go b/template/template_test.go index 3c2a18f60..ec9822e77 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -236,6 +236,26 @@ func TestSockAddr_Parse(t *testing.T) { input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | sort "+type,+address" | math "network" "-4278190088" | join "address" " " }}`, output: `127.255.255.248 ::1 fe80::ffff:ffff:ff:fff8`, }, + { + // Assume the private IPs available on the host are: 10.1.2.3 + // fe80::1025:f732:1001:203 + name: "GetPrivateIPs", + input: `{{GetPrivateIPs}}`, + output: `10.1.2.3 fe80::1025:f732:1001:203`, + }, + { + // Assume the public IPs available on the host are: 1.2.3.4 6.7.8.9 + name: "GetPublicIPs", + input: `{{GetPublicIPs}}`, + output: `1.2.3.4 6.7.8.9`, + }, + { + // Assume the private IPs on this host are just the IPv4 addresses: + // 10.1.2.3 and 172.16.4.6 + name: "GetInterfaceIPs", + input: `{{GetInterfaceIPs "en0"}}`, + output: `10.1.2.3 and 172.16.4.6`, + }, } for i, test := range tests {