-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Better interface selection heuristic #3546
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,7 +187,6 @@ func (n *NetworkFingerprint) isDeviceLoopBackOrPointToPoint(intf *net.Interface) | |
// and finds one which is routable and marked as UP | ||
// It excludes PPP and lo devices unless they are specifically asked | ||
func (f *NetworkFingerprint) findInterface(deviceName string) (*net.Interface, error) { | ||
var interfaces []net.Interface | ||
var err error | ||
|
||
if deviceName != "" { | ||
|
@@ -200,14 +199,57 @@ func (f *NetworkFingerprint) findInterface(deviceName string) (*net.Interface, e | |
return nil, err | ||
} | ||
|
||
// Since we aren't given an interface to use we have to pick using a | ||
// heuristic. To choose between interfaces we avoid down interfaces, those | ||
// that are loopback, point to point, or don't have an address. Of the | ||
// remaining interfaces we rank them positively for each global unicast | ||
// address and slightly negatively for a link local address. This makes | ||
// us select interfaces that have more globally routable address over those | ||
// that do not. | ||
var bestChoice *net.Interface | ||
bestScore := -1000 | ||
for _, intf := range intfs { | ||
if f.isDeviceEnabled(&intf) && !f.isDeviceLoopBackOrPointToPoint(&intf) && f.deviceHasIpAddress(&intf) { | ||
interfaces = append(interfaces, intf) | ||
// We require a non-loopback interface that is enabled with a valid | ||
// address. | ||
if !f.isDeviceEnabled(&intf) || f.isDeviceLoopBackOrPointToPoint(&intf) || !f.deviceHasIpAddress(&intf) { | ||
continue | ||
} | ||
|
||
addrs, err := f.interfaceDetector.Addrs(&intf) | ||
if err != nil { | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 We could |
||
} | ||
} | ||
|
||
if len(interfaces) == 0 { | ||
return nil, nil | ||
seenLinkLocal := false | ||
intfScore := 0 | ||
for _, addr := range addrs { | ||
// Find the IP Addr and the CIDR from the Address | ||
var ip net.IP | ||
switch v := (addr).(type) { | ||
case *net.IPNet: | ||
ip = v.IP | ||
case *net.IPAddr: | ||
ip = v.IP | ||
} | ||
|
||
if !seenLinkLocal && (ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()) { | ||
intfScore -= 1 | ||
seenLinkLocal = true | ||
} | ||
|
||
if ip.IsGlobalUnicast() { | ||
intfScore += 2 | ||
} | ||
} | ||
|
||
if intfScore > bestScore { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If infscore stays at zero and bestScore at -1000 this will pick the first |
||
// Make a copy on the stack so we grab a pointer to the correct | ||
// interface | ||
local := intf | ||
bestChoice = &local | ||
bestScore = intfScore | ||
} | ||
} | ||
return &interfaces[0], nil | ||
|
||
return bestChoice, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,7 +113,8 @@ type NetworkInterfaceDetectorMultipleInterfaces struct { | |
} | ||
|
||
func (n *NetworkInterfaceDetectorMultipleInterfaces) Interfaces() ([]net.Interface, error) { | ||
return []net.Interface{lo, eth0, eth1, eth2}, nil | ||
// Return link local first to test we don't prefer it | ||
return []net.Interface{eth3, lo, eth0, eth1, eth2, eth4}, nil | ||
} | ||
|
||
func (n *NetworkInterfaceDetectorMultipleInterfaces) InterfaceByName(name string) (*net.Interface, error) { | ||
|
@@ -301,7 +302,9 @@ func TestNetworkFingerPrint_default_device(t *testing.T) { | |
} | ||
} | ||
|
||
func TestNetworkFingerPrint_excludelo_down_interfaces(t *testing.T) { | ||
// This tests that we prefer skipping link local and down interfaces as well as | ||
// those without global unicast addresses | ||
func TestNetworkFingerPrint_preferential_interfaces(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see how any of these tests are exercising the new code path with the scoring introduced above. |
||
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}} | ||
node := &structs.Node{ | ||
Attributes: make(map[string]string), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the comments on the PR indicated that the returned list here is not stable (can return things out of order). What happens if there is more than one interface with a global unicast address, thus resulting in them getting the same score?
Reading the code below, it seems like it will choose the first one, but if the order changes when calling
interfaceDetector.Interfaces()
that will change the results.