Change:
What happened: When attempting to kubeadm join a control plane node or worker on a machine with global IPv4 unicast addresses on a loopback address (router to the host w/ BGP Unnumbered), the join fails with the following output: See this issue for details : Also: What you expected to happen: The correct IPv4 address should be selected from the loopback adapter, or we should be able to specify an IPv4 address to use for egress. The IPv6 addresses on the network adapters themselves are only used to establish upstream peering. IPv4 nexthops are fake 169.254.x.x addresses artificially programmed via RFC5549 into the ARP table. How to reproduce it (as minimally and precisely as possible): On a node with the following conditions: Routing to the host with FRR IPv6 link locals on physical network interfaces No IPv4 addresses on physical network interfaces Unicast IPv4 on the loopback adapter IPv4 default route present via RFC5549 Attempt to kubeadm join a node, and it will fail. Anything else we need to know?: We've been patching kubeadm with , which works as expected. There was a fix applied to allow initing control plane nodes w/ bind address 127.0.0.1 which would then select an IPv4 from the loopback, but it doesn't appear to have fixed the joining issues. Pinging and by request on kubeadm issues. Environment: Kubernetes version (use ): 1.17.12 Cloud provider or hardware configuration: baremetal OS (e.g: ): Centos 7 Kernel (e.g. ): 3.10.0-1127.19.1.el7.x86_64 Install tools: kubeadm Network plugin and version (if this is a network-related bug): Calico Others: FRR for peering with routers on physical network interfaces
/sig api-machinery
is defined but we should note the same function is used for the --bind-address of the kube-apiserver. /sig network
/assign
/triage accepted

Discussion:
return nil, nil } <ins> // 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 } </ins> // 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 {

} // chooseHostInterfaceFromRoute cycles through each default route provided, looking for a <del> // global IP address from the interface for the route. addressFamilies determines whether it // prefers IPv4 or IPv6 </del> <ins> // 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 </ins> 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))

klog.V(4).Infof("Found active IP %v ", finalIP) return finalIP, nil } <ins> // 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 } </ins> } } klog.V(4).Infof("No active IP found by looking at default routes")

return []net.Interface{p2pIntf}, nil } <ins> // 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 } </ins> // Unable to get IP addresses for interface type networkInterfaceFailGetAddrs struct { }

} } <ins> 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) } } } </ins> func TestChooseHostInterfaceFromRoute(t *testing.T) { testCases := []struct { tcase    string

{"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")}, <ins> {"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}, </ins> {"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil}, {"no routes", noRoutes, validNetworkInterface{}, preferIPv4, nil}, {"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil},