blob: f96b0e8d1621781d492d2dc20b0d4ebe9312664b [file] [log] [blame]
// Package netstate provides routines to obtain the available set of
// of network addresess, for determining changes to those addresses and for
// selecting from amongst them according to some set of policies that are
// implemented by applying simple predicates (functions with names of the form
// Is<condition>) to filter or find the first matching address from a list
// of addresses. The intent is to make it easy to create policies that do
// things like 'find the first IPv4 unicast address that is globally routable,
// failing that use a private IPv4 address, and failing that, an IPv6 address'.
//
// A typical usage would be:
//
// state, _ := netstate.GetAccessibleIPs()
// first := state.First(netstate.IsPublicIPv4)
// // first will contain the first public IPv4 address or be nil.
//
// The example policy described above would be implemented using a
// series of calls to First with appropriate predicates.
//
// Although most commercial networking hardware supports IPv6, some consumer
// devices and more importantly many ISPs do not, so routines are provided
// to allow developers to easily distinguish between the two and to use
// whichever is appropriate for their product/situation.
//
// The term 'accessible' is used to refer to any non-loopback IP address.
// The term 'public' is used to refer to any globally routable IP address.
//
// All IPv6 addresses are intended to be 'public', but any starting with
// fc00::/7 (RFC4193) are reserved for private use, but the go
// net libraries do not appear to recognise this. Similarly fe80::/10
// (RFC 4291) are reserved for 'site-local' usage, but again this is not
// implemented in the go libraries. Any developer who needs to distinguish
// these cases will need to write their own routines to test for them.
//
// When using the go net package it is important to remember that IPv6
// addresses subsume IPv4 and hence in many cases the same internal
// representation is used for both, thus testing for the length of the IP
// address is unreliable. The reliable test is to use the net.To4() which
// will return a non-nil result if can be used as an IPv4 one. Any address
// can be used as an IPv6 and hence the only reliable way to test for an IPv6
// address that is not an IPv4 one also is for the To4 call to return nil for
// it.
package netstate
import (
"fmt"
"net"
"strings"
)
type AddrList []net.Addr
func (al AddrList) String() string {
r := ""
for _, v := range al {
r += fmt.Sprintf("(%s) ", v)
}
return strings.TrimRight(r, " ")
}
func FromIPAddr(a []*net.IPAddr) AddrList {
var al AddrList
for _, a := range a {
al = append(al, a)
}
return al
}
// GetAll gets all of the available addresses on the device, including
// loopback addresses.
func GetAll() (AddrList, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
var all AddrList
for _, ifc := range interfaces {
addrs, err := ifc.Addrs()
if err != nil {
continue
}
all = append(all, addrs...)
}
return all, nil
}
// GetAccessibleIPs returns all of the IP addresses on the device that are
// accessible to other devices - i.e. excluding loopback etc.
// The IP addresses returned will be host addresses.
func GetAccessibleIPs() (AddrList, error) {
all, err := GetAll()
if err != nil {
return nil, err
}
return all.Filter(IsAccessibleIP).Map(ConvertToIPHost), nil
}
type Predicate func(a net.Addr) bool
// Filter returns all of the addresses for which the predicate
// function is true.
func (al AddrList) Filter(predicate Predicate) AddrList {
r := AddrList{}
for _, a := range al {
if predicate(a) {
r = append(r, a)
}
}
return r
}
// Filter returns the first address for which the predicate function is true.
func (al AddrList) First(predicate Predicate) net.Addr {
for _, a := range al {
if predicate(a) {
return a
}
}
return nil
}
type Mapper func(a net.Addr) net.Addr
// Map will apply the Mapper function to all of the items in its receiver
// and return a new AddrList containing all of the non-nil results from
// said calls.
func (al AddrList) Map(mapper Mapper) AddrList {
var ral AddrList
for _, a := range al {
if na := mapper(a); na != nil {
ral = append(ral, na)
}
}
return ral
}
// Convert the net.Addr argument into an instance of net.Addr that contains
// an IP host address (as opposed to a network CIDR for example).
func ConvertToIPHost(a net.Addr) net.Addr {
return AsIPAddr(a)
}
// IsIPProtocol returns true if its parameter is one of the allowed
// network/protocol values for IP.
func IsIPProtocol(n string) bool {
switch n {
case "ip+net", "tcp", "tcp4", "tcp6", "udp":
return true
default:
return false
}
}
// AsIPAddr returns its argument as a net.IPAddr if that's possible.
func AsIPAddr(a net.Addr) *net.IPAddr {
if v, ok := a.(*net.IPAddr); ok {
return v
}
if ipn, ok := a.(*net.IPNet); ok {
return &net.IPAddr{IP: ipn.IP}
}
switch a.Network() {
case "ip+net", "tcp", "tcp4", "tcp6", "udp":
if r := net.ParseIP(a.String()); r != nil {
return &net.IPAddr{IP: r}
}
}
return nil
}
// AsIP returns its argument as a net.IP if that's possible.
func AsIP(a net.Addr) net.IP {
ipAddr := AsIPAddr(a)
if ipAddr == nil {
return nil
}
return ipAddr.IP
}
// IsUnspecified returns true if its argument is an unspecified IP address
func IsUnspecifiedIP(a net.Addr) bool {
if ip := AsIP(a); ip != nil {
return ip.IsUnspecified()
}
return false
}
// IsLoopback returns true if its argument is a loopback IP address
func IsLoopbackIP(a net.Addr) bool {
if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
return ip.IsLoopback()
}
return false
}
// IsAccessible returns true if its argument is an accessible (non-loopback)
// IP address.
func IsAccessibleIP(a net.Addr) bool {
if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
return !ip.IsLoopback()
}
return false
}
type ma struct {
n, a string
}
func (a *ma) Network() string {
return a.n
}
func (a *ma) String() string {
return a.a
}
// AsAddr returns its argument as a net.Addr.
func AsAddr(network string, a net.IP) net.Addr {
return &ma{n: network, a: a.String()}
}
// IsUnicastIP returns true if its argument is a unicast IP address.
func IsUnicastIP(a net.Addr) bool {
if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
// ipv4 or v6
return !(ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
}
return false
}
// IsUnicastIPv4 returns true if its argument is a unicast IP4 address
func IsUnicastIPv4(a net.Addr) bool {
if ip := AsIP(a); ip != nil && ip.To4() != nil && !ip.IsMulticast() {
return !ip.IsUnspecified() && !ip.IsMulticast()
}
return false
}
// IsPublicUnicastIPv4 returns true if its argument is a globally routable,
// public IPv4 unicast address.
func IsPublicUnicastIPv4(a net.Addr) bool {
if ip := AsIP(a); ip != nil && !ip.IsUnspecified() {
if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
return !ip.IsMulticast()
}
}
return false
}
// IsUnicastIPv6 returns true if its argument is a unicast IPv6 address
func IsUnicastIPv6(a net.Addr) bool {
if ip := AsIP(a); ip != nil && ip.To4() == nil {
return !ip.IsUnspecified() && !(ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
}
return false
}
// IsUnicastIPv6 returns true if its argument is a globally routable IP6 address
func IsPublicUnicastIPv6(a net.Addr) bool {
if ip := AsIP(a); ip != nil && ip.To4() == nil {
if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
return true
}
}
return false
}
// IsPublicUnicastIP returns true if its argument is a global routable IPv4 or 6
// address.
func IsPublicUnicastIP(a net.Addr) bool {
if ip := AsIP(a); ip != nil {
if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
return true
}
if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
return true
}
}
return false
}
func diffAB(a, b AddrList) AddrList {
diff := AddrList{}
for _, av := range a {
found := false
for _, bv := range b {
if av.Network() == bv.Network() &&
av.String() == bv.String() {
found = true
break
}
}
if !found {
diff = append(diff, av)
}
}
return diff
}
// FindAdded returns the set of interfaces and/or addresses that are
// present in b, but not in a - i.e. have been added.
func FindAdded(a, b AddrList) AddrList {
return diffAB(b, a)
}
// FindRemoved returns the set of interfaces and/or addresses that
// are present in a, but not in b - i.e. have been removed.
func FindRemoved(a, b AddrList) AddrList {
return diffAB(a, b)
}