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

Infer subnet for node /128 IPv6 addresses #2338

Merged
merged 1 commit into from
Jul 15, 2021
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
8 changes: 7 additions & 1 deletion go-controller/pkg/node/gateway_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ func getNetworkInterfaceIPAddresses(iface string) ([]*net.IPNet, error) {
for _, ip := range allIPs {
if utilnet.IsIPv6CIDR(ip) {
if config.IPv6Mode && !foundIPv6 {
ips = append(ips, ip)
// For IPv6 addresses with 128 prefix, let's try to find an appropriate subnet
// in the routing table
subnetIP, err := util.GetIPv6OnSubnet(iface, ip)
if err != nil {
return nil, fmt.Errorf("could not find IPv6 address on subnet: %v", err)
}
ips = append(ips, subnetIP)
foundIPv6 = true
}
} else if config.IPv4Mode && !foundIPv4 {
Expand Down
33 changes: 33 additions & 0 deletions go-controller/pkg/util/net_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,36 @@ func GetNetworkInterfaceIPs(iface string) ([]*net.IPNet, error) {
}
return ips, nil
}

// GetIPv6OnSubnet when given an IPv6 address with a 128 prefix for an interface,
// looks for possible broadest subnet on-link routes and returns the same address
// with the found subnet prefix. Otherwise it returns the provided address unchanged.
func GetIPv6OnSubnet(iface string, ip *net.IPNet) (*net.IPNet, error) {
if s, _ := ip.Mask.Size(); s != 128 {
return ip, nil
}

link, err := netLinkOps.LinkByName(iface)
if err != nil {
return nil, fmt.Errorf("failed to lookup link %s: %v", iface, err)
}

routeFilter := &netlink.Route{
LinkIndex: link.Attrs().Index,
Gw: nil,
}
filterMask := netlink.RT_FILTER_GW | netlink.RT_FILTER_OIF
routes, err := netLinkOps.RouteListFiltered(netlink.FAMILY_V6, routeFilter, filterMask)
if err != nil {
return nil, fmt.Errorf("failed to get on-link routes for ip %s and iface %s", ip.String(), iface)
}

dst := *ip
for _, route := range routes {
if route.Dst.Contains(dst.IP) && !dst.Contains(route.Dst.IP) {
dst.Mask = route.Dst.Mask
}
}

return &dst, nil
}
112 changes: 112 additions & 0 deletions go-controller/pkg/util/net_linux_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,3 +858,115 @@ func TestDeleteConntrack(t *testing.T) {
})
}
}

func TestGetIPv6OnSubnet(t *testing.T) {
mockNetLinkOps := new(mocks.NetLinkOps)
mockLink := new(netlink_mocks.Link)
// below is defined in net_linux.go
netLinkOps = mockNetLinkOps

tests := []struct {
desc string
errExp bool
inputIface string
inputIP *net.IPNet
expectedIP *net.IPNet
onRetArgsNetLinkLibOpers []ovntest.TestifyMockHelper
onRetArgsLinkIfaceOpers []ovntest.TestifyMockHelper
}{
{
desc: "tests code path when LinkByName() returns error",
inputIface: "testIfaceName",
inputIP: ovntest.MustParseIPNet("2620:52:0:11c::20/128"),
expectedIP: nil,
errExp: true,
onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "LinkByName", OnCallMethodArgType: []string{"string"}, RetArgList: []interface{}{nil, fmt.Errorf("mock error")}},
},
},
{
desc: "tests code path when RouteListFiltered() returns error",
inputIface: "testIfaceName",
inputIP: ovntest.MustParseIPNet("2620:52:0:11c::20/128"),
expectedIP: nil,
errExp: true,
onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "LinkByName", OnCallMethodArgType: []string{"string"}, RetArgList: []interface{}{mockLink, nil}},
{OnCallMethodName: "RouteListFiltered", OnCallMethodArgType: []string{"int", "*netlink.Route", "uint64"}, RetArgList: []interface{}{nil, fmt.Errorf("mock error")}},
},
onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "Attrs", OnCallMethodArgType: []string{}, RetArgList: []interface{}{&netlink.LinkAttrs{Name: "testIfaceName", Index: 1}}},
},
},
{
desc: "provided address does not have 128 prefix",
inputIface: "testIfaceName",
inputIP: ovntest.MustParseIPNet("2620:52:0:11c::20/64"),
expectedIP: ovntest.MustParseIPNet("2620:52:0:11c::20/64"),
errExp: false,
onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{},
},
{
desc: "provided address is not ipv6",
inputIface: "testIfaceName",
inputIP: ovntest.MustParseIPNet("192.169.1.12/32"),
expectedIP: ovntest.MustParseIPNet("192.169.1.12/32"),
errExp: false,
onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{},
},
{
desc: "returns address on broadest subnet found",
inputIface: "testIfaceName",
inputIP: ovntest.MustParseIPNet("2620:52:0:11c::20/128"),
expectedIP: ovntest.MustParseIPNet("2620:52:0:11c::20/32"),
errExp: false,
onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "LinkByName", OnCallMethodArgType: []string{"string"}, RetArgList: []interface{}{mockLink, nil}},
{OnCallMethodName: "RouteListFiltered", OnCallMethodArgType: []string{"int", "*netlink.Route", "uint64"}, RetArgList: []interface{}{[]netlink.Route{
{Dst: ovntest.MustParseIPNet("2620:52:0:11c::/64")},
{Dst: ovntest.MustParseIPNet("2620:52::/32")},
}, nil}},
},
onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "Attrs", OnCallMethodArgType: []string{}, RetArgList: []interface{}{&netlink.LinkAttrs{Name: "testIfaceName", Index: 1}}},
},
},
{
desc: "returns address on broadest subnet found irrespective of order",
inputIface: "testIfaceName",
inputIP: ovntest.MustParseIPNet("2620:52:0:11c::20/128"),
expectedIP: ovntest.MustParseIPNet("2620:52:0:11c::20/32"),
errExp: false,
onRetArgsNetLinkLibOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "LinkByName", OnCallMethodArgType: []string{"string"}, RetArgList: []interface{}{mockLink, nil}},
{OnCallMethodName: "RouteListFiltered", OnCallMethodArgType: []string{"int", "*netlink.Route", "uint64"}, RetArgList: []interface{}{[]netlink.Route{
{Dst: ovntest.MustParseIPNet("2620:52::/32")},
{Dst: ovntest.MustParseIPNet("2620:52:0:11c::/64")},
}, nil}},
},
onRetArgsLinkIfaceOpers: []ovntest.TestifyMockHelper{
{OnCallMethodName: "Attrs", OnCallMethodArgType: []string{}, RetArgList: []interface{}{&netlink.LinkAttrs{Name: "testIfaceName", Index: 1}}},
},
},
}
for i, tc := range tests {
t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) {

ovntest.ProcessMockFnList(&mockNetLinkOps.Mock, tc.onRetArgsNetLinkLibOpers)
ovntest.ProcessMockFnList(&mockLink.Mock, tc.onRetArgsLinkIfaceOpers)

ip, err := GetIPv6OnSubnet(tc.inputIface, tc.inputIP)
t.Log(ip, err)
if tc.errExp {
assert.Error(t, err)
} else {
assert.Nil(t, err)
}
if tc.expectedIP != nil {
assert.Equal(t, ip, tc.expectedIP)
}
mockNetLinkOps.AssertExpectations(t)
mockLink.AssertExpectations(t)
})
}
}