autodetect global addresses on loopback interfaces

There are some network scenarios that, in order to have high availability based
on routing protocols, you may have multiple interfaces in the host and you use
a routing protocol to configure the routing in the host.
It is common to not use global addresses on those interfaces because you don't want
them to be reachable, so you assign the global address to the loopback interface.

Loopback interfaces are always up, regardless of the states of physical interfaces.
They are not subject to interface problems, i.e., if the interface is down or flapping
you can not reach the IP despite you have connectivity through another interface.

We should consider global ip addresses on loopback interfaces when:
- the host has default routes
- there are no global IPs on those interfaces.

There can be more cases in which you have global addresses on the interfaces too,
but that will open an explosion of scenarios hard to support and to "autodetect"
It will be a cluster admin responsability to configure the network in such
scenarios.
This commit is contained in:
Antonio Ojea
2020-10-22 12:46:30 +02:00
parent ededd08ba1
commit 078d2e65b5
2 changed files with 73 additions and 2 deletions

View File

@@ -393,8 +393,9 @@ func getAllDefaultRoutes() ([]Route, error) {
}
// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a
// global IP address from the interface for the route. addressFamilies determines whether it
// prefers IPv4 or IPv6
// global IP address from the interface for the route. If there are routes but no global
// address is obtained from the interfaces, it checks if the loopback interface has a global address.
// addressFamilies determines whether it prefers IPv4 or IPv6
func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
for _, family := range addressFamilies {
klog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family))
@@ -411,6 +412,17 @@ func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressF
klog.V(4).Infof("Found active IP %v ", finalIP)
return finalIP, nil
}
// In case of network setups where default routes are present, but network
// interfaces use only link-local addresses (e.g. as described in RFC5549).
// the global IP is assigned to the loopback interface
loopbackIP, err := getIPFromInterface(LoopbackInterfaceName, family, nw)
if err != nil {
return nil, err
}
if loopbackIP != nil {
klog.V(4).Infof("Found active IP %v on Loopback interface", loopbackIP)
return loopbackIP, nil
}
}
}
klog.V(4).Infof("No active IP found by looking at default routes")

View File

@@ -453,6 +453,55 @@ func (_ p2pNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{p2pIntf}, nil
}
// Interface with link locals and loopback interface with global addresses
type linkLocalLoopbackNetworkInterface struct {
}
func (_ linkLocalLoopbackNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
if intfName == LoopbackInterfaceName {
return &loopbackIntf, nil
}
return &upIntf, nil
}
func (_ linkLocalLoopbackNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}}
if intf.Name == LoopbackInterfaceName {
ifat = []net.Addr{addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"},
// global addresses on loopback interface
addrStruct{val: "10.1.1.1/32"}, addrStruct{val: "fd00:1:1::1/128"}}
}
return ifat, nil
}
func (_ linkLocalLoopbackNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf, loopbackIntf}, nil
}
// Interface and loopback interface with global addresses
type globalsNetworkInterface struct {
}
func (_ globalsNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
if intfName == LoopbackInterfaceName {
return &loopbackIntf, nil
}
return &upIntf, nil
}
func (_ globalsNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
var ifat []net.Addr
ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"},
addrStruct{val: "192.168.1.1/31"}, addrStruct{val: "fd00::200/127"}}
if intf.Name == LoopbackInterfaceName {
ifat = []net.Addr{addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"},
// global addresses on loopback interface
addrStruct{val: "10.1.1.1/32"}, addrStruct{val: "fd00:1:1::1/128"}}
}
return ifat, nil
}
func (_ globalsNetworkInterface) Interfaces() ([]net.Interface, error) {
return []net.Interface{upIntf, loopbackIntf}, nil
}
// Unable to get IP addresses for interface
type networkInterfaceFailGetAddrs struct {
}
@@ -544,6 +593,16 @@ func TestChooseHostInterfaceFromRoute(t *testing.T) {
{"single-stack ipv6, prefer v6", routeV6, ipv6NetworkInterface{}, preferIPv6, net.ParseIP("2001::200")},
{"dual stack", bothRoutes, v4v6NetworkInterface{}, preferIPv4, net.ParseIP("10.254.71.145")},
{"dual stack, prefer v6", bothRoutes, v4v6NetworkInterface{}, preferIPv6, net.ParseIP("2001::10")},
{"LLA and loopback with global, IPv4", routeV4, linkLocalLoopbackNetworkInterface{}, preferIPv4, net.ParseIP("10.1.1.1")},
{"LLA and loopback with global, IPv6", routeV6, linkLocalLoopbackNetworkInterface{}, preferIPv6, net.ParseIP("fd00:1:1::1")},
{"LLA and loopback with global, dual stack prefer IPv4", bothRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv4, net.ParseIP("10.1.1.1")},
{"LLA and loopback with global, dual stack prefer IPv6", bothRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv6, net.ParseIP("fd00:1:1::1")},
{"LLA and loopback with global, no routes", noRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv6, nil},
{"interface and loopback with global, IPv4", routeV4, globalsNetworkInterface{}, preferIPv4, net.ParseIP("192.168.1.1")},
{"interface and loopback with global, IPv6", routeV6, globalsNetworkInterface{}, preferIPv6, net.ParseIP("fd00::200")},
{"interface and loopback with global, dual stack prefer IPv4", bothRoutes, globalsNetworkInterface{}, preferIPv4, net.ParseIP("192.168.1.1")},
{"interface and loopback with global, dual stack prefer IPv6", bothRoutes, globalsNetworkInterface{}, preferIPv6, net.ParseIP("fd00::200")},
{"interface and loopback with global, no routes", noRoutes, globalsNetworkInterface{}, preferIPv6, nil},
{"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil},
{"no routes", noRoutes, validNetworkInterface{}, preferIPv4, nil},
{"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil},