When a macvlan's parent interface is down it's not possible
to send NA messages. So, ignore the error.
Signed-off-by: Rob Murray <rob.murray@docker.com>
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
"github.com/docker/docker/testutil/daemon" |
| 20 | 20 |
"gotest.tools/v3/assert" |
| 21 | 21 |
is "gotest.tools/v3/assert/cmp" |
| 22 |
+ "gotest.tools/v3/icmd" |
|
| 22 | 23 |
"gotest.tools/v3/skip" |
| 23 | 24 |
) |
| 24 | 25 |
|
| ... | ... |
@@ -670,3 +671,28 @@ func TestEndpointWithCustomIfname(t *testing.T) {
|
| 670 | 670 |
assert.NilError(t, err) |
| 671 | 671 |
assert.Assert(t, strings.Contains(out.Stdout, ": foobar@if"), "expected ': foobar@if' in 'ip link show':\n%s", out.Stdout) |
| 672 | 672 |
} |
| 673 |
+ |
|
| 674 |
+// TestParentDown checks that when a macvlan's parent is down, a container can still |
|
| 675 |
+// be attached. |
|
| 676 |
+// Regression test for https://github.com/moby/moby/issues/49593 |
|
| 677 |
+func TestParentDown(t *testing.T) {
|
|
| 678 |
+ skip.If(t, testEnv.IsRemoteDaemon) |
|
| 679 |
+ skip.If(t, testEnv.IsRootless, "rootless mode has different view of network") |
|
| 680 |
+ |
|
| 681 |
+ ctx := setupTest(t) |
|
| 682 |
+ apiClient := testEnv.APIClient() |
|
| 683 |
+ |
|
| 684 |
+ const tap = "dummytap0" |
|
| 685 |
+ res := icmd.RunCommand("ip", "tuntap", "add", "mode", "tap", tap)
|
|
| 686 |
+ res.Assert(t, icmd.Success) |
|
| 687 |
+ defer icmd.RunCommand("ip", "link", "del", tap)
|
|
| 688 |
+ |
|
| 689 |
+ const netName = "testnet-macvlan" |
|
| 690 |
+ net.CreateNoError(ctx, t, apiClient, netName, |
|
| 691 |
+ net.WithMacvlan(tap), |
|
| 692 |
+ net.WithIPv6(), |
|
| 693 |
+ ) |
|
| 694 |
+ |
|
| 695 |
+ ctrID := container.Run(ctx, t, apiClient, container.WithNetworkMode(netName)) |
|
| 696 |
+ defer container.Remove(ctx, t, apiClient, ctrID, containertypes.RemoveOptions{Force: true})
|
|
| 697 |
+} |
| ... | ... |
@@ -300,8 +300,8 @@ func (n *Namespace) AddInterface(ctx context.Context, srcName, dstPrefix, dstNam |
| 300 | 300 |
if err := waitForBridgePort(ctx, ns.NlHandle(), iface); err != nil {
|
| 301 | 301 |
return fmt.Errorf("check bridge port state: %w", err)
|
| 302 | 302 |
} |
| 303 |
- waitForMcastRoute(ctx, iface.Attrs().Index, i, n.nlHandle) |
|
| 304 |
- if err := n.advertiseAddrs(ctx, iface.Attrs().Index, i, n.nlHandle); err != nil {
|
|
| 303 |
+ mcastRouteOk := waitForMcastRoute(ctx, iface.Attrs().Index, i, n.nlHandle) |
|
| 304 |
+ if err := n.advertiseAddrs(ctx, iface.Attrs().Index, i, n.nlHandle, mcastRouteOk); err != nil {
|
|
| 305 | 305 |
return fmt.Errorf("failed to advertise addresses: %w", err)
|
| 306 | 306 |
} |
| 307 | 307 |
} |
| ... | ... |
@@ -606,9 +606,9 @@ func stpEnabled(ctx context.Context, name string) bool {
|
| 606 | 606 |
// That error has not been seen since addition of the check that the veth's parent bridge port |
| 607 | 607 |
// is forwarding, so that may have been the issue. But, in case it's a timing problem that's |
| 608 | 608 |
// only less-likely because of delay caused by that check, make sure the route exists. |
| 609 |
-func waitForMcastRoute(ctx context.Context, ifIndex int, i *Interface, nlh nlwrap.Handle) {
|
|
| 609 |
+func waitForMcastRoute(ctx context.Context, ifIndex int, i *Interface, nlh nlwrap.Handle) bool {
|
|
| 610 | 610 |
if i.addressIPv6 == nil || i.advertiseAddrNMsgs == 0 {
|
| 611 |
- return |
|
| 611 |
+ return true |
|
| 612 | 612 |
} |
| 613 | 613 |
ctx, span := otel.Tracer("").Start(ctx, "libnetwork.osl.waitForMcastRoute")
|
| 614 | 614 |
defer span.End() |
| ... | ... |
@@ -624,16 +624,17 @@ func waitForMcastRoute(ctx context.Context, ifIndex int, i *Interface, nlh nlwra |
| 624 | 624 |
// FIXME(robmry) - if EMSGSIZE is returned (why?), it seems to be persistent. |
| 625 | 625 |
// So, skip the delay and continue to the NA send as it seems to succeed. |
| 626 | 626 |
log.G(ctx).Info("Skipping check for route to send NA, EMSGSIZE")
|
| 627 |
- return |
|
| 627 |
+ return true |
|
| 628 | 628 |
} |
| 629 | 629 |
if err != nil || len(routes) == 0 {
|
| 630 | 630 |
log.G(ctx).WithFields(log.Fields{"error": err, "nroutes": len(routes)}).Info("Waiting for route to send NA")
|
| 631 | 631 |
time.Sleep(pollInterval) |
| 632 | 632 |
continue |
| 633 | 633 |
} |
| 634 |
- return |
|
| 634 |
+ return true |
|
| 635 | 635 |
} |
| 636 | 636 |
log.G(ctx).WithField("", maxWait).Warn("No route for neighbour advertisement")
|
| 637 |
+ return false |
|
| 637 | 638 |
} |
| 638 | 639 |
|
| 639 | 640 |
// advertiseAddrs triggers send unsolicited ARP and Neighbour Advertisement |
| ... | ... |
@@ -646,16 +647,17 @@ func waitForMcastRoute(ctx context.Context, ifIndex int, i *Interface, nlh nlwra |
| 646 | 646 |
// until the ARP/Neighbour cache entries expire. |
| 647 | 647 |
// |
| 648 | 648 |
// Note that the kernel's arp_notify sysctl setting is not respected. |
| 649 |
-func (n *Namespace) advertiseAddrs(ctx context.Context, ifIndex int, i *Interface, nlh nlwrap.Handle) error {
|
|
| 649 |
+func (n *Namespace) advertiseAddrs(ctx context.Context, ifIndex int, i *Interface, nlh nlwrap.Handle, mcastRouteOk bool) error {
|
|
| 650 | 650 |
mac := i.MacAddress() |
| 651 | 651 |
address4 := i.Address() |
| 652 | 652 |
address6 := i.AddressIPv6() |
| 653 | 653 |
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
|
| 654 |
- "iface": i.dstName, |
|
| 655 |
- "ifi": ifIndex, |
|
| 656 |
- "mac": mac.String(), |
|
| 657 |
- "ip4": address4, |
|
| 658 |
- "ip6": address6, |
|
| 654 |
+ "iface": i.dstName, |
|
| 655 |
+ "ifi": ifIndex, |
|
| 656 |
+ "mac": mac.String(), |
|
| 657 |
+ "ip4": address4, |
|
| 658 |
+ "ip6": address6, |
|
| 659 |
+ "mcastRouteOk": mcastRouteOk, |
|
| 659 | 660 |
})) |
| 660 | 661 |
|
| 661 | 662 |
if address4 == nil && address6 == nil {
|
| ... | ... |
@@ -712,7 +714,11 @@ func (n *Namespace) advertiseAddrs(ctx context.Context, ifIndex int, i *Interfac |
| 712 | 712 |
if naSender != nil {
|
| 713 | 713 |
if err := naSender.Send(); err != nil {
|
| 714 | 714 |
log.G(ctx).WithError(err).Warn("Failed to send unsolicited NA")
|
| 715 |
- errs = append(errs, err) |
|
| 715 |
+ // If there was no multicast route and the network is unreachable, ignore the |
|
| 716 |
+ // error - this happens when a macvlan's parent interface is down. |
|
| 717 |
+ if mcastRouteOk || !errors.Is(err, unix.ENETUNREACH) {
|
|
| 718 |
+ errs = append(errs, err) |
|
| 719 |
+ } |
|
| 716 | 720 |
} |
| 717 | 721 |
} |
| 718 | 722 |
return errors.Join(errs...) |