Merge pull request #95790 from aojea/loopnet

autodetect global addresses on loopback interfaces
This commit is contained in:
Kubernetes Prow Robot 2020-12-08 16:29:36 -08:00 committed by GitHub
commit 403d0cdc28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 130 additions and 2 deletions

View File

@ -266,6 +266,36 @@ func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInte
return nil, nil
}
// getIPFromLoopbackInterface gets the IPs on a loopback interface and returns a global unicast address, if any.
// The loopback interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
}
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
continue
}
if intf.Flags&(net.FlagLoopback) != 0 {
addrs, err := nw.Addrs(&intf)
if err != nil {
return nil, err
}
klog.V(4).Infof("Interface %q has %d addresses :%v.", intf.Name, len(addrs), addrs)
matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
if err != nil {
return nil, err
}
if matchingIP != nil {
klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intf.Name)
return matchingIP, nil
}
}
}
return nil, nil
}
// memberOf tells if the IP is of the desired family. Used for checking interface addresses.
func memberOf(ip net.IP, family AddressFamily) bool {
if ip.To4() != nil {
@ -393,8 +423,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 +442,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, and we should use it
loopbackIP, err := getIPFromLoopbackInterface(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 {
}
@ -530,6 +579,33 @@ func TestGetIPFromInterface(t *testing.T) {
}
}
func TestGetIPFromLoopbackInterface(t *testing.T) {
testCases := []struct {
tcase string
family AddressFamily
nw networkInterfacer
expected net.IP
errStrFrag string
}{
{"ipv4", familyIPv4, linkLocalLoopbackNetworkInterface{}, net.ParseIP("10.1.1.1"), ""},
{"ipv6", familyIPv6, linkLocalLoopbackNetworkInterface{}, net.ParseIP("fd00:1:1::1"), ""},
{"no global ipv4", familyIPv4, loopbackNetworkInterface{}, nil, ""},
{"no global ipv6", familyIPv6, loopbackNetworkInterface{}, nil, ""},
}
for _, tc := range testCases {
ip, err := getIPFromLoopbackInterface(tc.family, tc.nw)
if err != nil {
if !strings.Contains(err.Error(), tc.errStrFrag) {
t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag)
}
} else if tc.errStrFrag != "" {
t.Errorf("case[%s]: Error %q expected, but seen %v", tc.tcase, tc.errStrFrag, err)
} else if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err)
}
}
}
func TestChooseHostInterfaceFromRoute(t *testing.T) {
testCases := []struct {
tcase string
@ -544,6 +620,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},