blob: 0ab9b36080b5c135db9a44a07a188df13ac7c9db [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package net
18
19import (
20 "bufio"
21 "encoding/hex"
22 "fmt"
23 "io"
24 "net"
25 "os"
26
27 "strings"
28
29 "github.com/golang/glog"
30)
31
32type AddressFamily uint
33
34const (
35 familyIPv4 AddressFamily = 4
36 familyIPv6 AddressFamily = 6
37)
38
39const (
40 ipv4RouteFile = "/proc/net/route"
41 ipv6RouteFile = "/proc/net/ipv6_route"
42)
43
44type Route struct {
45 Interface string
46 Destination net.IP
47 Gateway net.IP
48 Family AddressFamily
49}
50
51type RouteFile struct {
52 name string
53 parse func(input io.Reader) ([]Route, error)
54}
55
56// noRoutesError can be returned by ChooseBindAddress() in case of no routes
57type noRoutesError struct {
58 message string
59}
60
61func (e noRoutesError) Error() string {
62 return e.message
63}
64
65// IsNoRoutesError checks if an error is of type noRoutesError
66func IsNoRoutesError(err error) bool {
67 if err == nil {
68 return false
69 }
70 switch err.(type) {
71 case noRoutesError:
72 return true
73 default:
74 return false
75 }
76}
77
78var (
79 v4File = RouteFile{name: ipv4RouteFile, parse: getIPv4DefaultRoutes}
80 v6File = RouteFile{name: ipv6RouteFile, parse: getIPv6DefaultRoutes}
81)
82
83func (rf RouteFile) extract() ([]Route, error) {
84 file, err := os.Open(rf.name)
85 if err != nil {
86 return nil, err
87 }
88 defer file.Close()
89 return rf.parse(file)
90}
91
92// getIPv4DefaultRoutes obtains the IPv4 routes, and filters out non-default routes.
93func getIPv4DefaultRoutes(input io.Reader) ([]Route, error) {
94 routes := []Route{}
95 scanner := bufio.NewReader(input)
96 for {
97 line, err := scanner.ReadString('\n')
98 if err == io.EOF {
99 break
100 }
101 //ignore the headers in the route info
102 if strings.HasPrefix(line, "Iface") {
103 continue
104 }
105 fields := strings.Fields(line)
106 // Interested in fields:
107 // 0 - interface name
108 // 1 - destination address
109 // 2 - gateway
110 dest, err := parseIP(fields[1], familyIPv4)
111 if err != nil {
112 return nil, err
113 }
114 gw, err := parseIP(fields[2], familyIPv4)
115 if err != nil {
116 return nil, err
117 }
118 if !dest.Equal(net.IPv4zero) {
119 continue
120 }
121 routes = append(routes, Route{
122 Interface: fields[0],
123 Destination: dest,
124 Gateway: gw,
125 Family: familyIPv4,
126 })
127 }
128 return routes, nil
129}
130
131func getIPv6DefaultRoutes(input io.Reader) ([]Route, error) {
132 routes := []Route{}
133 scanner := bufio.NewReader(input)
134 for {
135 line, err := scanner.ReadString('\n')
136 if err == io.EOF {
137 break
138 }
139 fields := strings.Fields(line)
140 // Interested in fields:
141 // 0 - destination address
142 // 4 - gateway
143 // 9 - interface name
144 dest, err := parseIP(fields[0], familyIPv6)
145 if err != nil {
146 return nil, err
147 }
148 gw, err := parseIP(fields[4], familyIPv6)
149 if err != nil {
150 return nil, err
151 }
152 if !dest.Equal(net.IPv6zero) {
153 continue
154 }
155 if gw.Equal(net.IPv6zero) {
156 continue // loopback
157 }
158 routes = append(routes, Route{
159 Interface: fields[9],
160 Destination: dest,
161 Gateway: gw,
162 Family: familyIPv6,
163 })
164 }
165 return routes, nil
166}
167
168// parseIP takes the hex IP address string from route file and converts it
169// to a net.IP address. For IPv4, the value must be converted to big endian.
170func parseIP(str string, family AddressFamily) (net.IP, error) {
171 if str == "" {
172 return nil, fmt.Errorf("input is nil")
173 }
174 bytes, err := hex.DecodeString(str)
175 if err != nil {
176 return nil, err
177 }
178 if family == familyIPv4 {
179 if len(bytes) != net.IPv4len {
180 return nil, fmt.Errorf("invalid IPv4 address in route")
181 }
182 return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil
183 }
184 // Must be IPv6
185 if len(bytes) != net.IPv6len {
186 return nil, fmt.Errorf("invalid IPv6 address in route")
187 }
188 return net.IP(bytes), nil
189}
190
191func isInterfaceUp(intf *net.Interface) bool {
192 if intf == nil {
193 return false
194 }
195 if intf.Flags&net.FlagUp != 0 {
196 glog.V(4).Infof("Interface %v is up", intf.Name)
197 return true
198 }
199 return false
200}
201
202func isLoopbackOrPointToPoint(intf *net.Interface) bool {
203 return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0
204}
205
206// getMatchingGlobalIP returns the first valid global unicast address of the given
207// 'family' from the list of 'addrs'.
208func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) {
209 if len(addrs) > 0 {
210 for i := range addrs {
211 glog.V(4).Infof("Checking addr %s.", addrs[i].String())
212 ip, _, err := net.ParseCIDR(addrs[i].String())
213 if err != nil {
214 return nil, err
215 }
216 if memberOf(ip, family) {
217 if ip.IsGlobalUnicast() {
218 glog.V(4).Infof("IP found %v", ip)
219 return ip, nil
220 } else {
221 glog.V(4).Infof("Non-global unicast address found %v", ip)
222 }
223 } else {
224 glog.V(4).Infof("%v is not an IPv%d address", ip, int(family))
225 }
226
227 }
228 }
229 return nil, nil
230}
231
232// getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The
233// interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
234func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
235 intf, err := nw.InterfaceByName(intfName)
236 if err != nil {
237 return nil, err
238 }
239 if isInterfaceUp(intf) {
240 addrs, err := nw.Addrs(intf)
241 if err != nil {
242 return nil, err
243 }
244 glog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs)
245 matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
246 if err != nil {
247 return nil, err
248 }
249 if matchingIP != nil {
250 glog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName)
251 return matchingIP, nil
252 }
253 }
254 return nil, nil
255}
256
257// memberOF tells if the IP is of the desired family. Used for checking interface addresses.
258func memberOf(ip net.IP, family AddressFamily) bool {
259 if ip.To4() != nil {
260 return family == familyIPv4
261 } else {
262 return family == familyIPv6
263 }
264}
265
266// chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that
267// has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP.
268// Searches for IPv4 addresses, and then IPv6 addresses.
269func chooseIPFromHostInterfaces(nw networkInterfacer) (net.IP, error) {
270 intfs, err := nw.Interfaces()
271 if err != nil {
272 return nil, err
273 }
274 if len(intfs) == 0 {
275 return nil, fmt.Errorf("no interfaces found on host.")
276 }
277 for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
278 glog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family))
279 for _, intf := range intfs {
280 if !isInterfaceUp(&intf) {
281 glog.V(4).Infof("Skipping: down interface %q", intf.Name)
282 continue
283 }
284 if isLoopbackOrPointToPoint(&intf) {
285 glog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name)
286 continue
287 }
288 addrs, err := nw.Addrs(&intf)
289 if err != nil {
290 return nil, err
291 }
292 if len(addrs) == 0 {
293 glog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name)
294 continue
295 }
296 for _, addr := range addrs {
297 ip, _, err := net.ParseCIDR(addr.String())
298 if err != nil {
299 return nil, fmt.Errorf("Unable to parse CIDR for interface %q: %s", intf.Name, err)
300 }
301 if !memberOf(ip, family) {
302 glog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name)
303 continue
304 }
305 // TODO: Decide if should open up to allow IPv6 LLAs in future.
306 if !ip.IsGlobalUnicast() {
307 glog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name)
308 continue
309 }
310 glog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name)
311 return ip, nil
312 }
313 }
314 }
315 return nil, fmt.Errorf("no acceptable interface with global unicast address found on host")
316}
317
318// ChooseHostInterface is a method used fetch an IP for a daemon.
319// If there is no routing info file, it will choose a global IP from the system
320// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the
321// IP of the interface with a gateway on it (with priority given to IPv4). For a node
322// with no internet connection, it returns error.
323func ChooseHostInterface() (net.IP, error) {
324 var nw networkInterfacer = networkInterface{}
325 if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) {
326 return chooseIPFromHostInterfaces(nw)
327 }
328 routes, err := getAllDefaultRoutes()
329 if err != nil {
330 return nil, err
331 }
332 return chooseHostInterfaceFromRoute(routes, nw)
333}
334
335// networkInterfacer defines an interface for several net library functions. Production
336// code will forward to net library functions, and unit tests will override the methods
337// for testing purposes.
338type networkInterfacer interface {
339 InterfaceByName(intfName string) (*net.Interface, error)
340 Addrs(intf *net.Interface) ([]net.Addr, error)
341 Interfaces() ([]net.Interface, error)
342}
343
344// networkInterface implements the networkInterfacer interface for production code, just
345// wrapping the underlying net library function calls.
346type networkInterface struct{}
347
348func (_ networkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
349 return net.InterfaceByName(intfName)
350}
351
352func (_ networkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
353 return intf.Addrs()
354}
355
356func (_ networkInterface) Interfaces() ([]net.Interface, error) {
357 return net.Interfaces()
358}
359
360// getAllDefaultRoutes obtains IPv4 and IPv6 default routes on the node. If unable
361// to read the IPv4 routing info file, we return an error. If unable to read the IPv6
362// routing info file (which is optional), we'll just use the IPv4 route information.
363// Using all the routing info, if no default routes are found, an error is returned.
364func getAllDefaultRoutes() ([]Route, error) {
365 routes, err := v4File.extract()
366 if err != nil {
367 return nil, err
368 }
369 v6Routes, _ := v6File.extract()
370 routes = append(routes, v6Routes...)
371 if len(routes) == 0 {
372 return nil, noRoutesError{
373 message: fmt.Sprintf("no default routes found in %q or %q", v4File.name, v6File.name),
374 }
375 }
376 return routes, nil
377}
378
379// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a
380// global IP address from the interface for the route. Will first look all each IPv4 route for
381// an IPv4 IP, and then will look at each IPv6 route for an IPv6 IP.
382func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer) (net.IP, error) {
383 for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
384 glog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family))
385 for _, route := range routes {
386 if route.Family != family {
387 continue
388 }
389 glog.V(4).Infof("Default route transits interface %q", route.Interface)
390 finalIP, err := getIPFromInterface(route.Interface, family, nw)
391 if err != nil {
392 return nil, err
393 }
394 if finalIP != nil {
395 glog.V(4).Infof("Found active IP %v ", finalIP)
396 return finalIP, nil
397 }
398 }
399 }
400 glog.V(4).Infof("No active IP found by looking at default routes")
401 return nil, fmt.Errorf("unable to select an IP from default routes.")
402}
403
404// If bind-address is usable, return it directly
405// If bind-address is not usable (unset, 0.0.0.0, or loopback), we will use the host's default
406// interface.
407func ChooseBindAddress(bindAddress net.IP) (net.IP, error) {
408 if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() {
409 hostIP, err := ChooseHostInterface()
410 if err != nil {
411 return nil, err
412 }
413 bindAddress = hostIP
414 }
415 return bindAddress, nil
416}