diff --git a/pkg/ipam/ipam_linux.go b/pkg/ipam/ipam_linux.go index 4c5af045..b1814667 100644 --- a/pkg/ipam/ipam_linux.go +++ b/pkg/ipam/ipam_linux.go @@ -29,7 +29,8 @@ import ( const ( // Note: use slash as separator so we can have dots in interface name (VLANs) - DisableIPv6SysctlTemplate = "net/ipv6/conf/%s/disable_ipv6" + DisableIPv6SysctlTemplate = "net/ipv6/conf/%s/disable_ipv6" + KeepAddrOnDownSysctlTemplate = "net/ipv6/conf/%s/keep_addr_on_down" ) // ConfigureIface takes the result of IPAM plugin and @@ -56,8 +57,8 @@ func ConfigureIface(ifName string, res *current.Result) error { return fmt.Errorf("failed to add IP addr %v to %q: invalid interface index", ipc, ifName) } - // Make sure sysctl "disable_ipv6" is 0 if we are about to add - // an IPv6 address to the interface + // Make sure sysctl "disable_ipv6" is 0 and "keep_addr_on_down" is 1 + // if we are about to add an IPv6 address to the interface if !hasEnabledIpv6 && ipc.Address.IP.To4() == nil { // Enabled IPv6 for loopback "lo" and the interface // being configured @@ -80,6 +81,15 @@ func ConfigureIface(ifName string, res *current.Result) error { return fmt.Errorf("failed to enable IPv6 for interface %q (%s=%s): %v", iface, ipv6SysctlValueName, value, err) } } + + // Enable "keep_addr_on_down" for the interface being configured + // This prevents the kernel from removing the address when the interface is brought down + keepAddrOnDownSysctlValueName := fmt.Sprintf(KeepAddrOnDownSysctlTemplate, ifName) + _, err = sysctl.Sysctl(keepAddrOnDownSysctlValueName, "1") + if err != nil { + return fmt.Errorf("failed to enable keep_addr_on_down for interface %q: %v", ifName, err) + } + hasEnabledIpv6 = true } diff --git a/pkg/ipam/ipam_linux_test.go b/pkg/ipam/ipam_linux_test.go index 9afbcc1c..2de58c21 100644 --- a/pkg/ipam/ipam_linux_test.go +++ b/pkg/ipam/ipam_linux_test.go @@ -201,6 +201,49 @@ var _ = Describe("ConfigureIface", func() { Expect(err).NotTo(HaveOccurred()) }) + It("keeps IPV6 addresses after the interface is brought down", func() { + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + By("Configuring the interface") + + err := ConfigureIface(LINK_NAME, result) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying the IPV6 address is present") + + link, err := netlinksafe.LinkByName(LINK_NAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(LINK_NAME)) + + v6addrs, err := netlinksafe.AddrList(link, syscall.AF_INET6) + Expect(err).NotTo(HaveOccurred()) + Expect(v6addrs).To(HaveLen(2)) + + var found bool + for _, a := range v6addrs { + if ipNetEqual(a.IPNet, ipv6) { + found = true + break + } + } + Expect(found).To(BeTrue()) + + By("Bringing the interface down") + err = netlink.LinkSetDown(link) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying the IPV6 address is still present") + v6addrs, err = netlinksafe.AddrList(link, syscall.AF_INET6) + Expect(err).NotTo(HaveOccurred()) + Expect(v6addrs).To(HaveLen(1)) + Expect(ipNetEqual(v6addrs[0].IPNet, ipv6)).To(BeTrue()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + It("configures a link with routes using address gateways", func() { result.Routes[0].GW = nil result.Routes[1].GW = nil