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