blob: e9a61a5b13557ad0acdaf2a86101bd8e20c67bc1 [file] [log] [blame]
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -07001// Package netstate provides routines to obtain the available set of
2// of network addresess, for determining changes to those addresses and for
3// selecting from amongst them according to some set of policies that are
4// implemented by applying simple predicates (functions with names of the form
Cosmos Nicolaouef323db2014-09-07 22:13:28 -07005// Is<condition>) to filter or find the first matching address from a list
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -07006// of addresses. The intent is to make it easy to create policies that do
7// things like 'find the first IPv4 unicast address that is globally routable,
8// failing that use a private IPv4 address, and failing that, an IPv6 address'.
9//
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -070010// A simple usage would be:
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -070011//
12// state, _ := netstate.GetAccessibleIPs()
Bogdan Caprita06829fd2014-11-06 15:56:28 -080013// ipv4 := state.Filter(netstate.IsPublicUnicastIPv4)
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -070014// // ipv4 will contain all of the public IPv4 addresses, if any.
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -070015//
16// The example policy described above would be implemented using a
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -070017// series of calls to Filter with appropriate predicates.
18//
19// In some cases, it may be necessary to take IP routing information
20// into account and hence interface hosting the address. The interface
21// hosting each address is provided in the AddrIfc structure used to represent
22// addresses and the IP routing information is provided by the GetAccessibleIPs
23// function which will typically be used to obtain the available IP addresses.
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -070024//
25// Although most commercial networking hardware supports IPv6, some consumer
26// devices and more importantly many ISPs do not, so routines are provided
27// to allow developers to easily distinguish between the two and to use
28// whichever is appropriate for their product/situation.
29//
30// The term 'accessible' is used to refer to any non-loopback IP address.
31// The term 'public' is used to refer to any globally routable IP address.
32//
33// All IPv6 addresses are intended to be 'public', but any starting with
Cosmos Nicolaouef323db2014-09-07 22:13:28 -070034// fc00::/7 (RFC4193) are reserved for private use, but the go
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -070035// net libraries do not appear to recognise this. Similarly fe80::/10
36// (RFC 4291) are reserved for 'site-local' usage, but again this is not
37// implemented in the go libraries. Any developer who needs to distinguish
38// these cases will need to write their own routines to test for them.
39//
40// When using the go net package it is important to remember that IPv6
41// addresses subsume IPv4 and hence in many cases the same internal
42// representation is used for both, thus testing for the length of the IP
43// address is unreliable. The reliable test is to use the net.To4() which
44// will return a non-nil result if can be used as an IPv4 one. Any address
45// can be used as an IPv6 and hence the only reliable way to test for an IPv6
46// address that is not an IPv4 one also is for the To4 call to return nil for
47// it.
48package netstate
49
50import (
51 "fmt"
52 "net"
53 "strings"
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -070054
55 "veyron.io/veyron/veyron2/ipc"
56
57 "veyron.io/veyron/veyron/lib/netconfig"
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -070058)
59
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -070060// AddrIfc represents a network address and the network interface that
61// hosts it.
62type AddrIfc struct {
63 // Network address
64 Addr net.Addr
65
66 // The name of the network interface this address is hosted on, empty
67 // if this information is not available.
68 Name string
69
70 // The IPRoutes of the network interface this address is hosted on,
71 // nil if this information is not available.
72 IPRoutes []*netconfig.IPRoute
73}
74
75func (a *AddrIfc) String() string {
76 if a.IPRoutes != nil {
77 r := fmt.Sprintf("%s: %s[", a.Addr, a.Name)
78 for _, rt := range a.IPRoutes {
79 src := ""
80 if rt.PreferredSource != nil {
81 src = ", src: " + rt.PreferredSource.String()
82 }
83 r += fmt.Sprintf("{%d: net: %s, gw: %s%s}, ", rt.IfcIndex, rt.Net, rt.Gateway, src)
84 }
85 r = strings.TrimSuffix(r, ", ")
86 r += "]"
87 return r
88 }
89 return a.Addr.String()
90}
91
92func (a *AddrIfc) Address() net.Addr {
93 return a.Addr
94}
95
96func (a *AddrIfc) InterfaceIndex() int {
97 if len(a.IPRoutes) == 0 {
98 return -1
99 }
100 return a.IPRoutes[0].IfcIndex
101}
102
103func (a *AddrIfc) InterfaceName() string {
104 return a.Name
105}
106
107func (a *AddrIfc) Networks() []net.Addr {
108 nets := []net.Addr{}
109 for _, r := range a.IPRoutes {
110 nets = append(nets, &r.Net)
111 }
112 return nets
113}
114
115type AddrList []ipc.Address
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700116
117func (al AddrList) String() string {
118 r := ""
119 for _, v := range al {
120 r += fmt.Sprintf("(%s) ", v)
121 }
122 return strings.TrimRight(r, " ")
123}
124
125// GetAll gets all of the available addresses on the device, including
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700126// loopback addresses, non-IP protocols etc.
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700127func GetAll() (AddrList, error) {
128 interfaces, err := net.Interfaces()
129 if err != nil {
130 return nil, err
131 }
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700132 routes := netconfig.GetIPRoutes(false)
133 routeTable := make(map[int][]*netconfig.IPRoute)
134 for _, r := range routes {
135 routeTable[r.IfcIndex] = append(routeTable[r.IfcIndex], r)
136 }
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700137 var all AddrList
138 for _, ifc := range interfaces {
139 addrs, err := ifc.Addrs()
140 if err != nil {
141 continue
142 }
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700143 for _, a := range addrs {
144 all = append(all, &AddrIfc{a, ifc.Name, routeTable[ifc.Index]})
145 }
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700146 }
147 return all, nil
148}
149
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700150// GetAccessibleIPs returns all of the accessible IP addresses on the device
151// - i.e. excluding loopback and unspecified addresses.
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700152// The IP addresses returned will be host addresses.
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700153func GetAccessibleIPs() (AddrList, error) {
154 all, err := GetAll()
155 if err != nil {
156 return nil, err
157 }
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700158 return all.Map(accessibleIPHost), nil
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700159}
160
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700161func accessibleIPHost(a ipc.Address) ipc.Address {
162 if !IsAccessibleIP(a) {
163 return nil
164 }
165 aifc, ok := a.(*AddrIfc)
166 if !ok {
167 return nil
168 }
169 ip := AsIPAddr(aifc.Addr)
170 if ip == nil {
171 return aifc
172 }
173 aifc.Addr = ip
174 return aifc
175}
176
177// AddressPredicate defines the function signature for predicate functions
178// to be used with AddrList
179type AddressPredicate func(a ipc.Address) bool
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700180
181// Filter returns all of the addresses for which the predicate
182// function is true.
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700183func (al AddrList) Filter(predicate AddressPredicate) AddrList {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700184 r := AddrList{}
185 for _, a := range al {
186 if predicate(a) {
187 r = append(r, a)
188 }
189 }
190 return r
191}
192
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700193type Mapper func(a ipc.Address) ipc.Address
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700194
195// Map will apply the Mapper function to all of the items in its receiver
196// and return a new AddrList containing all of the non-nil results from
197// said calls.
198func (al AddrList) Map(mapper Mapper) AddrList {
199 var ral AddrList
200 for _, a := range al {
201 if na := mapper(a); na != nil {
202 ral = append(ral, na)
203 }
204 }
205 return ral
206}
207
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700208// Convert the network address component of an ipc.Address into an instance
209// with a net.Addr that contains an IP host address (as opposed to a
210// network CIDR for example).
211func ConvertToIPHost(a ipc.Address) ipc.Address {
212 aifc, ok := a.(*AddrIfc)
213 if !ok {
214 return nil
215 }
216 aifc.Addr = AsIPAddr(aifc.Addr)
217 return aifc
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700218}
219
220// IsIPProtocol returns true if its parameter is one of the allowed
221// network/protocol values for IP.
222func IsIPProtocol(n string) bool {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700223 switch n {
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700224 case "ip+net", "ip", "tcp", "tcp4", "tcp6", "udp":
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700225 return true
226 default:
227 return false
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700228 }
229}
230
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700231// AsIPAddr returns its argument as a net.IPAddr if that's possible.
232func AsIPAddr(a net.Addr) *net.IPAddr {
233 if v, ok := a.(*net.IPAddr); ok {
234 return v
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700235 }
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700236 if ipn, ok := a.(*net.IPNet); ok {
237 return &net.IPAddr{IP: ipn.IP}
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700238 }
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700239 if IsIPProtocol(a.Network()) {
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700240 if r := net.ParseIP(a.String()); r != nil {
241 return &net.IPAddr{IP: r}
242 }
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700243 }
Cosmos Nicolaouef323db2014-09-07 22:13:28 -0700244 return nil
245}
246
247// AsIP returns its argument as a net.IP if that's possible.
248func AsIP(a net.Addr) net.IP {
249 ipAddr := AsIPAddr(a)
250 if ipAddr == nil {
251 return nil
252 }
253 return ipAddr.IP
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700254}
255
256// IsUnspecified returns true if its argument is an unspecified IP address
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700257func IsUnspecifiedIP(a ipc.Address) bool {
258 if ip := AsIP(a.Address()); ip != nil {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700259 return ip.IsUnspecified()
260 }
261 return false
262}
263
264// IsLoopback returns true if its argument is a loopback IP address
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700265func IsLoopbackIP(a ipc.Address) bool {
266 if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700267 return ip.IsLoopback()
268 }
269 return false
270}
271
272// IsAccessible returns true if its argument is an accessible (non-loopback)
273// IP address.
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700274func IsAccessibleIP(a ipc.Address) bool {
275 if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700276 return !ip.IsLoopback()
277 }
278 return false
279}
280
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700281// IsUnicastIP returns true if its argument is a unicast IP address.
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700282func IsUnicastIP(a ipc.Address) bool {
283 if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700284 // ipv4 or v6
285 return !(ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
286 }
287 return false
288}
289
290// IsUnicastIPv4 returns true if its argument is a unicast IP4 address
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700291func IsUnicastIPv4(a ipc.Address) bool {
292 if ip := AsIP(a.Address()); ip != nil && ip.To4() != nil {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700293 return !ip.IsUnspecified() && !ip.IsMulticast()
294 }
295 return false
296}
297
298// IsPublicUnicastIPv4 returns true if its argument is a globally routable,
299// public IPv4 unicast address.
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700300func IsPublicUnicastIPv4(a ipc.Address) bool {
301 if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700302 if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
303 return !ip.IsMulticast()
304 }
305 }
306 return false
307}
308
309// IsUnicastIPv6 returns true if its argument is a unicast IPv6 address
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700310func IsUnicastIPv6(a ipc.Address) bool {
311 if ip := AsIP(a.Address()); ip != nil && ip.To4() == nil {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700312 return !ip.IsUnspecified() && !(ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
313 }
314 return false
315}
316
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700317// IsUnicastIPv6 returns true if its argument is a globally routable IP6
318// address
319func IsPublicUnicastIPv6(a ipc.Address) bool {
320 if ip := AsIP(a.Address()); ip != nil && ip.To4() == nil {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700321 if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
322 return true
323 }
324 }
325 return false
326}
327
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700328// IsPublicUnicastIP returns true if its argument is a global routable IPv4
329// or 6 address.
330func IsPublicUnicastIP(a ipc.Address) bool {
331 if ip := AsIP(a.Address()); ip != nil {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700332 if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
333 return true
334 }
335 if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
336 return true
337 }
338 }
339 return false
340}
341
342func diffAB(a, b AddrList) AddrList {
343 diff := AddrList{}
344 for _, av := range a {
345 found := false
346 for _, bv := range b {
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700347 if av.Address().Network() == bv.Address().Network() &&
348 av.Address().String() == bv.Address().String() {
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700349 found = true
350 break
351 }
352 }
353 if !found {
354 diff = append(diff, av)
355 }
356 }
357 return diff
358}
359
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700360// FindAdded returns the set addresses that are present in b, but not
361// in a - i.e. have been added.
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700362func FindAdded(a, b AddrList) AddrList {
363 return diffAB(b, a)
364}
365
Cosmos Nicolaou66bc1202014-09-30 20:42:43 -0700366// FindRemoved returns the set of addresses that are present in a, but not
367// in b - i.e. have been removed.
Cosmos Nicolaouf7a11d92014-08-29 09:56:07 -0700368func FindRemoved(a, b AddrList) AddrList {
369 return diffAB(a, b)
370}
Robert Kroeger053c0682014-09-29 15:02:49 -0700371
372// SameMachine returns true if the provided addr is on the node
373// executing this function.
374func SameMachine(addr net.Addr) (bool, error) {
375 // The available interfaces may change between calls.
376 addrs, err := GetAll()
377 if err != nil {
378 return false, err
379 }
Robert Kroeger053c0682014-09-29 15:02:49 -0700380 ips := make(map[string]struct{})
381 for _, a := range addrs {
382 ip, _, err := net.ParseCIDR(a.Address().String())
383 if err != nil {
384 return false, err
385 }
386 ips[ip.String()] = struct{}{}
387 }
388
389 client, _, err := net.SplitHostPort(addr.String())
390 if err != nil {
391 return false, err
392 }
Robert Kroeger053c0682014-09-29 15:02:49 -0700393 _, islocal := ips[client]
394 return islocal, nil
395}