blob: daf5d249645599304f0eb3192f1526d7e00a13fb [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
David Bainbridge86971522019-09-26 22:09:39 +000029 "k8s.io/klog"
Zack Williamse940c7a2019-08-21 14:25:39 -070030)
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 {
David Bainbridge86971522019-09-26 22:09:39 +0000196 klog.V(4).Infof("Interface %v is up", intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700197 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 {
David Bainbridge86971522019-09-26 22:09:39 +0000211 klog.V(4).Infof("Checking addr %s.", addrs[i].String())
Zack Williamse940c7a2019-08-21 14:25:39 -0700212 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() {
David Bainbridge86971522019-09-26 22:09:39 +0000218 klog.V(4).Infof("IP found %v", ip)
Zack Williamse940c7a2019-08-21 14:25:39 -0700219 return ip, nil
220 } else {
David Bainbridge86971522019-09-26 22:09:39 +0000221 klog.V(4).Infof("Non-global unicast address found %v", ip)
Zack Williamse940c7a2019-08-21 14:25:39 -0700222 }
223 } else {
David Bainbridge86971522019-09-26 22:09:39 +0000224 klog.V(4).Infof("%v is not an IPv%d address", ip, int(family))
Zack Williamse940c7a2019-08-21 14:25:39 -0700225 }
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 }
David Bainbridge86971522019-09-26 22:09:39 +0000244 klog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs)
Zack Williamse940c7a2019-08-21 14:25:39 -0700245 matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
246 if err != nil {
247 return nil, err
248 }
249 if matchingIP != nil {
David Bainbridge86971522019-09-26 22:09:39 +0000250 klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName)
Zack Williamse940c7a2019-08-21 14:25:39 -0700251 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} {
David Bainbridge86971522019-09-26 22:09:39 +0000278 klog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family))
Zack Williamse940c7a2019-08-21 14:25:39 -0700279 for _, intf := range intfs {
280 if !isInterfaceUp(&intf) {
David Bainbridge86971522019-09-26 22:09:39 +0000281 klog.V(4).Infof("Skipping: down interface %q", intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700282 continue
283 }
284 if isLoopbackOrPointToPoint(&intf) {
David Bainbridge86971522019-09-26 22:09:39 +0000285 klog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700286 continue
287 }
288 addrs, err := nw.Addrs(&intf)
289 if err != nil {
290 return nil, err
291 }
292 if len(addrs) == 0 {
David Bainbridge86971522019-09-26 22:09:39 +0000293 klog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700294 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) {
David Bainbridge86971522019-09-26 22:09:39 +0000302 klog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700303 continue
304 }
305 // TODO: Decide if should open up to allow IPv6 LLAs in future.
306 if !ip.IsGlobalUnicast() {
David Bainbridge86971522019-09-26 22:09:39 +0000307 klog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700308 continue
309 }
David Bainbridge86971522019-09-26 22:09:39 +0000310 klog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name)
Zack Williamse940c7a2019-08-21 14:25:39 -0700311 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} {
David Bainbridge86971522019-09-26 22:09:39 +0000384 klog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family))
Zack Williamse940c7a2019-08-21 14:25:39 -0700385 for _, route := range routes {
386 if route.Family != family {
387 continue
388 }
David Bainbridge86971522019-09-26 22:09:39 +0000389 klog.V(4).Infof("Default route transits interface %q", route.Interface)
Zack Williamse940c7a2019-08-21 14:25:39 -0700390 finalIP, err := getIPFromInterface(route.Interface, family, nw)
391 if err != nil {
392 return nil, err
393 }
394 if finalIP != nil {
David Bainbridge86971522019-09-26 22:09:39 +0000395 klog.V(4).Infof("Found active IP %v ", finalIP)
Zack Williamse940c7a2019-08-21 14:25:39 -0700396 return finalIP, nil
397 }
398 }
399 }
David Bainbridge86971522019-09-26 22:09:39 +0000400 klog.V(4).Infof("No active IP found by looking at default routes")
Zack Williamse940c7a2019-08-21 14:25:39 -0700401 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}