Merge pull request #84727 from danwinship/ipv6-bind

fix apiserver to advertise IPv6 endpoints if bound to IPv6
This commit is contained in:
Kubernetes Prow Robot 2019-11-08 18:44:24 -08:00 committed by GitHub
commit 49a9b6cadf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 41 deletions

View File

@ -139,10 +139,10 @@ func VerifyAPIServerBindAddress(address string) error {
return nil
}
// ChooseAPIServerBindAddress is a wrapper for netutil.ChooseBindAddress that also handles
// ChooseAPIServerBindAddress is a wrapper for netutil.ResolveBindAddress that also handles
// the case where no default routes were found and an IP for the API server could not be obtained.
func ChooseAPIServerBindAddress(bindAddress net.IP) (net.IP, error) {
ip, err := netutil.ChooseBindAddress(bindAddress)
ip, err := netutil.ResolveBindAddress(bindAddress)
if err != nil {
if netutil.IsNoRoutesError(err) {
klog.Warningf("WARNING: could not obtain a bind address for the API Server: %v; using: %s", err, constants.DefaultAPIServerBindAddress)

View File

@ -60,7 +60,7 @@ func DefaultAdvertiseAddress(s *genericoptions.ServerRunOptions, insecure *gener
}
if s.AdvertiseAddress == nil || s.AdvertiseAddress.IsUnspecified() {
hostIP, err := utilnet.ChooseBindAddress(insecure.BindAddress)
hostIP, err := utilnet.ResolveBindAddress(insecure.BindAddress)
if err != nil {
return fmt.Errorf("unable to find suitable network address.error='%v'. "+
"Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this", err)

View File

@ -36,6 +36,13 @@ const (
familyIPv6 AddressFamily = 6
)
type AddressFamilyPreference []AddressFamily
var (
preferIPv4 = AddressFamilyPreference{familyIPv4, familyIPv6}
preferIPv6 = AddressFamilyPreference{familyIPv6, familyIPv4}
)
const (
// LoopbackInterfaceName is the default name of the loopback interface
LoopbackInterfaceName = "lo"
@ -58,7 +65,7 @@ type RouteFile struct {
parse func(input io.Reader) ([]Route, error)
}
// noRoutesError can be returned by ChooseBindAddress() in case of no routes
// noRoutesError can be returned in case of no routes
type noRoutesError struct {
message string
}
@ -259,7 +266,7 @@ func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInte
return nil, nil
}
// memberOF tells if the IP is of the desired family. Used for checking interface addresses.
// 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 {
return family == familyIPv4
@ -270,8 +277,8 @@ func memberOf(ip net.IP, family AddressFamily) bool {
// chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that
// has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP.
// Searches for IPv4 addresses, and then IPv6 addresses.
func chooseIPFromHostInterfaces(nw networkInterfacer) (net.IP, error) {
// addressFamilies determines whether it prefers IPv4 or IPv6
func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
@ -279,7 +286,7 @@ func chooseIPFromHostInterfaces(nw networkInterfacer) (net.IP, error) {
if len(intfs) == 0 {
return nil, fmt.Errorf("no interfaces found on host.")
}
for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
for _, family := range addressFamilies {
klog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family))
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
@ -326,15 +333,19 @@ func chooseIPFromHostInterfaces(nw networkInterfacer) (net.IP, error) {
// IP of the interface with a gateway on it (with priority given to IPv4). For a node
// with no internet connection, it returns error.
func ChooseHostInterface() (net.IP, error) {
return chooseHostInterface(preferIPv4)
}
func chooseHostInterface(addressFamilies AddressFamilyPreference) (net.IP, error) {
var nw networkInterfacer = networkInterface{}
if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) {
return chooseIPFromHostInterfaces(nw)
return chooseIPFromHostInterfaces(nw, addressFamilies)
}
routes, err := getAllDefaultRoutes()
if err != nil {
return nil, err
}
return chooseHostInterfaceFromRoute(routes, nw)
return chooseHostInterfaceFromRoute(routes, nw, addressFamilies)
}
// networkInterfacer defines an interface for several net library functions. Production
@ -382,10 +393,10 @@ func getAllDefaultRoutes() ([]Route, error) {
}
// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a
// global IP address from the interface for the route. Will first look all each IPv4 route for
// an IPv4 IP, and then will look at each IPv6 route for an IPv6 IP.
func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer) (net.IP, error) {
for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
// global IP address from the interface for the route. 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))
for _, route := range routes {
if route.Family != family {
@ -406,12 +417,19 @@ func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer) (net.IP,
return nil, fmt.Errorf("unable to select an IP from default routes.")
}
// If bind-address is usable, return it directly
// If bind-address is not usable (unset, 0.0.0.0, or loopback), we will use the host's default
// interface.
func ChooseBindAddress(bindAddress net.IP) (net.IP, error) {
// ResolveBindAddress returns the IP address of a daemon, based on the given bindAddress:
// If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface().
// If bindAddress is unspecified or loopback, it returns the default IP of the same
// address family as bindAddress.
// Otherwise, it just returns bindAddress.
func ResolveBindAddress(bindAddress net.IP) (net.IP, error) {
addressFamilies := preferIPv4
if bindAddress != nil && memberOf(bindAddress, familyIPv6) {
addressFamilies = preferIPv6
}
if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() {
hostIP, err := ChooseHostInterface()
hostIP, err := chooseHostInterface(addressFamilies)
if err != nil {
return nil, err
}
@ -426,7 +444,7 @@ func ChooseBindAddress(bindAddress net.IP) (net.IP, error) {
// 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.
func ChooseBindAddressForInterface(intfName string) (net.IP, error) {
var nw networkInterfacer = networkInterface{}
for _, family := range []AddressFamily{familyIPv4, familyIPv6} {
for _, family := range preferIPv4 {
ip, err := getIPFromInterface(intfName, family, nw)
if err != nil {
return nil, err

View File

@ -535,17 +535,21 @@ func TestChooseHostInterfaceFromRoute(t *testing.T) {
tcase string
routes []Route
nw networkInterfacer
order AddressFamilyPreference
expected net.IP
}{
{"ipv4", routeV4, validNetworkInterface{}, net.ParseIP("10.254.71.145")},
{"ipv6", routeV6, ipv6NetworkInterface{}, net.ParseIP("2001::200")},
{"prefer ipv4", bothRoutes, v4v6NetworkInterface{}, net.ParseIP("10.254.71.145")},
{"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, nil},
{"no routes", noRoutes, validNetworkInterface{}, nil},
{"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, nil},
{"single-stack ipv4", routeV4, validNetworkInterface{}, preferIPv4, net.ParseIP("10.254.71.145")},
{"single-stack ipv4, prefer v6", routeV4, validNetworkInterface{}, preferIPv6, net.ParseIP("10.254.71.145")},
{"single-stack ipv6", routeV6, ipv6NetworkInterface{}, preferIPv4, net.ParseIP("2001::200")},
{"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")},
{"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil},
{"no routes", noRoutes, validNetworkInterface{}, preferIPv4, nil},
{"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil},
}
for _, tc := range testCases {
ip, err := chooseHostInterfaceFromRoute(tc.routes, tc.nw)
ip, err := chooseHostInterfaceFromRoute(tc.routes, tc.nw, tc.order)
if !ip.Equal(tc.expected) {
t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err)
}
@ -575,24 +579,29 @@ func TestGetIPFromHostInterfaces(t *testing.T) {
testCases := []struct {
tcase string
nw networkInterfacer
order AddressFamilyPreference
expected net.IP
errStrFrag string
}{
{"fail get I/Fs", failGettingNetworkInterface{}, nil, "failed getting all interfaces"},
{"no interfaces", noNetworkInterface{}, nil, "no interfaces"},
{"I/F not up", downNetworkInterface{}, nil, "no acceptable"},
{"loopback only", loopbackNetworkInterface{}, nil, "no acceptable"},
{"P2P I/F only", p2pNetworkInterface{}, nil, "no acceptable"},
{"fail get addrs", networkInterfaceFailGetAddrs{}, nil, "unable to get Addrs"},
{"no addresses", networkInterfaceWithNoAddrs{}, nil, "no acceptable"},
{"invalid addr", networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"},
{"no matches", networkInterfaceWithOnlyLinkLocals{}, nil, "no acceptable"},
{"ipv4", validNetworkInterface{}, net.ParseIP("10.254.71.145"), ""},
{"ipv6", ipv6NetworkInterface{}, net.ParseIP("2001::200"), ""},
{"fail get I/Fs", failGettingNetworkInterface{}, preferIPv4, nil, "failed getting all interfaces"},
{"no interfaces", noNetworkInterface{}, preferIPv4, nil, "no interfaces"},
{"I/F not up", downNetworkInterface{}, preferIPv4, nil, "no acceptable"},
{"loopback only", loopbackNetworkInterface{}, preferIPv4, nil, "no acceptable"},
{"P2P I/F only", p2pNetworkInterface{}, preferIPv4, nil, "no acceptable"},
{"fail get addrs", networkInterfaceFailGetAddrs{}, preferIPv4, nil, "unable to get Addrs"},
{"no addresses", networkInterfaceWithNoAddrs{}, preferIPv4, nil, "no acceptable"},
{"invalid addr", networkInterfaceWithInvalidAddr{}, preferIPv4, nil, "invalid CIDR"},
{"no matches", networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil, "no acceptable"},
{"single-stack ipv4", validNetworkInterface{}, preferIPv4, net.ParseIP("10.254.71.145"), ""},
{"single-stack ipv4, prefer ipv6", validNetworkInterface{}, preferIPv6, net.ParseIP("10.254.71.145"), ""},
{"single-stack ipv6", ipv6NetworkInterface{}, preferIPv4, net.ParseIP("2001::200"), ""},
{"single-stack ipv6, prefer ipv6", ipv6NetworkInterface{}, preferIPv6, net.ParseIP("2001::200"), ""},
{"dual stack", v4v6NetworkInterface{}, preferIPv4, net.ParseIP("10.254.71.145"), ""},
{"dual stack, prefer ipv6", v4v6NetworkInterface{}, preferIPv6, net.ParseIP("2001::10"), ""},
}
for _, tc := range testCases {
ip, err := chooseIPFromHostInterfaces(tc.nw)
ip, err := chooseIPFromHostInterfaces(tc.nw, tc.order)
if !ip.Equal(tc.expected) {
t.Errorf("case[%s]: expected %+v, got %+v with err : %v", tc.tcase, tc.expected, ip, err)
}

View File

@ -109,10 +109,10 @@ func NewSecureServingOptions() *SecureServingOptions {
}
func (s *SecureServingOptions) DefaultExternalAddress() (net.IP, error) {
if !s.ExternalAddress.IsUnspecified() {
if s.ExternalAddress != nil && !s.ExternalAddress.IsUnspecified() {
return s.ExternalAddress, nil
}
return utilnet.ChooseBindAddress(s.BindAddress)
return utilnet.ResolveBindAddress(s.BindAddress)
}
func (s *SecureServingOptions) Validate() []error {