Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

autodetect global addresses on loopback interfaces #95790

Merged
merged 2 commits into from Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 44 additions & 2 deletions staging/src/k8s.io/apimachinery/pkg/util/net/interface.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand All @@ -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")
Expand Down
86 changes: 86 additions & 0 deletions staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go
Expand Up @@ -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 {
}
Expand Down Expand Up @@ -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
Expand All @@ -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},
Comment on lines +629 to +632
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liggitt these are the tests that verify that we don't modify previous behaviour, 192.168.1.1 and fd00::200 are the ones assigned to the interfaces and we check that take precedence over the ones assigned to the loopback interfaces

{"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil},
{"no routes", noRoutes, validNetworkInterface{}, preferIPv4, nil},
{"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil},
Expand Down