Skip to content

Commit

Permalink
Add GetPrivateIPs, GetPublicIPs, and GetInterfaceIPs helper fun…
Browse files Browse the repository at this point in the history
…ctions.

Returns a space-delimited list of respective IP addresses.
  • Loading branch information
sean- committed May 23, 2017
1 parent 938ffc5 commit 4d74f98
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 21 deletions.
128 changes: 128 additions & 0 deletions ifaddr.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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" <<ARG>> | 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
Expand Down
54 changes: 54 additions & 0 deletions ifaddr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 {
Expand Down
21 changes: 1 addition & 20 deletions ifaddrs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions rfc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 26 additions & 1 deletion template/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,44 @@ 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.
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
Expand Down
14 changes: 14 additions & 0 deletions template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
20 changes: 20 additions & 0 deletions template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 4d74f98

Please sign in to comment.