Pārlūkot izejas kodu

Changes on top of dualStack PR

This patch addresses the reviews made on the original PR

Signed-off-by: Manuel Buil <mbuil@suse.com>
Manuel Buil 3 gadi atpakaļ
vecāks
revīzija
d23baf4462

+ 23 - 0
Documentation/configuration.md

@@ -80,3 +80,26 @@ Any command line option can be turned into an environment variable by prefixing
 Flannel provides a health check http endpoint `healthz`. Currently this endpoint will blindly
 Flannel provides a health check http endpoint `healthz`. Currently this endpoint will blindly
 return http status ok(i.e. 200) when flannel is running. This feature is by default disabled.
 return http status ok(i.e. 200) when flannel is running. This feature is by default disabled.
 Set `healthz-port` to a non-zero value will enable a healthz server for flannel.
 Set `healthz-port` to a non-zero value will enable a healthz server for flannel.
+
+## Dual-stack
+
+Flannel supports the dual-stack mode of Kubernetes. This means pods and services could use ipv4 and ipv6 at the same time. Currently, dual-stack is only supported for kube subnet manager and vxlan backend.
+
+Requirements:
+* v1.0 of flannel binary from [containernetworking/plugins](https://github.com/containernetworking/plugins)
+* Nodes must have an ipv4 and ipv6 address in the main interface
+* Nodes must have an ipv4 and ipv6 address default route
+* vxlan support ipv6 tunnel require kernel version >= 3.12
+
+Configuration:
+* Set flanneld daemon with "--kube-subnet-mgr" CLI option
+* Set "EnableIPv6": true and the "IPv6Network", for example "IPv6Network": "2001:cafe:42:0::/56" in the net-conf.json of the kube-flannel-cfg ConfigMap 
+
+If everything works as expected, flanneld should generate a `/run/flannel/subnet.env` file with IPV6 subnet and network. For example:
+
+FLANNEL_NETWORK=10.42.0.0/16
+FLANNEL_SUBNET=10.42.0.1/24
+FLANNEL_IPV6_NETWORK=2001:cafe:42::/56
+FLANNEL_IPV6_SUBNET=2001:cafe:42::1/64
+FLANNEL_MTU=1450
+FLANNEL_IPMASQ=true

+ 4 - 4
backend/vxlan/device.go

@@ -124,13 +124,13 @@ func (dev *vxlanDevice) Configure(ipa ip.IP4Net, flannelnet ip.IP4Net) error {
 	return nil
 	return nil
 }
 }
 
 
-func (dev *vxlanDevice) ConfigureIPv6(ipn ip.IP6Net) error {
-	if err := ip.EnsureV6AddressOnLink(ipn, dev.link); err != nil {
-		return fmt.Errorf("failed to ensure v6 address of interface %s: %s", dev.link.Attrs().Name, err)
+func (dev *vxlanDevice) ConfigureIPv6(ipn ip.IP6Net, flannelnet ip.IP6Net) error {
+	if err := ip.EnsureV6AddressOnLink(ipn, flannelnet, dev.link); err != nil {
+		return fmt.Errorf("failed to ensure v6 address of interface %s: %w", dev.link.Attrs().Name, err)
 	}
 	}
 
 
 	if err := netlink.LinkSetUp(dev.link); err != nil {
 	if err := netlink.LinkSetUp(dev.link); err != nil {
-		return fmt.Errorf("failed to set v6 interface %s to UP state: %s", dev.link.Attrs().Name, err)
+		return fmt.Errorf("failed to set v6 interface %s to UP state: %w", dev.link.Attrs().Name, err)
 	}
 	}
 
 
 	return nil
 	return nil

+ 7 - 8
backend/vxlan/vxlan.go

@@ -190,15 +190,14 @@ func (be *VXLANBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGroup,
 	// Ensure that the device has a /32 address so that no broadcast routes are created.
 	// Ensure that the device has a /32 address so that no broadcast routes are created.
 	// This IP is just used as a source address for host to workload traffic (so
 	// This IP is just used as a source address for host to workload traffic (so
 	// the return path for the traffic has an address on the flannel network to use as the destination)
 	// the return path for the traffic has an address on the flannel network to use as the destination)
-        if config.EnableIPv4 {
-                if err := dev.Configure(ip.IP4Net{IP: lease.Subnet.IP, PrefixLen: 32}, config.Network); err != nil {
-                        return nil, fmt.Errorf("failed to configure interface %s: %w", dev.link.Attrs().Name, err)
-                }
-        }
-
+	if config.EnableIPv4 {
+		if err := dev.Configure(ip.IP4Net{IP: lease.Subnet.IP, PrefixLen: 32}, config.Network); err != nil {
+			return nil, fmt.Errorf("failed to configure interface %s: %w", dev.link.Attrs().Name, err)
+		}
+	}
 	if config.EnableIPv6 {
 	if config.EnableIPv6 {
-		if err := v6Dev.ConfigureIPv6(ip.IP6Net{IP: lease.IPv6Subnet.IP, PrefixLen: 128}); err != nil {
-			return nil, fmt.Errorf("failed to configure interface %s: %s", v6Dev.link.Attrs().Name, err)
+		if err := v6Dev.ConfigureIPv6(ip.IP6Net{IP: lease.IPv6Subnet.IP, PrefixLen: 128}, config.IPv6Network); err != nil {
+			return nil, fmt.Errorf("failed to configure interface %s: %w", v6Dev.link.Attrs().Name, err)
 		}
 		}
 	}
 	}
 	return newNetwork(be.subnetMgr, be.extIface, dev, v6Dev, ip.IP4Net{}, lease)
 	return newNetwork(be.subnetMgr, be.extIface, dev, v6Dev, ip.IP4Net{}, lease)

+ 1 - 1
backend/vxlan/vxlan_network.go

@@ -96,7 +96,7 @@ func (nw *network) handleSubnetEvents(batch []subnet.Event) {
 		v6Sn := event.Lease.IPv6Subnet
 		v6Sn := event.Lease.IPv6Subnet
 		attrs := event.Lease.Attrs
 		attrs := event.Lease.Attrs
 		if attrs.BackendType != "vxlan" {
 		if attrs.BackendType != "vxlan" {
-			log.Warningf("ignoring non-vxlan v4Subnet(%s) v6Subnet: type=%v", sn, v6Sn, attrs.BackendType)
+			log.Warningf("ignoring non-vxlan v4Subnet(%s) v6Subnet(%s): type=%v", sn, v6Sn, attrs.BackendType)
 			continue
 			continue
 		}
 		}
 
 

+ 1 - 0
go.sum

@@ -73,6 +73,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

+ 61 - 57
main.go

@@ -134,8 +134,6 @@ func init() {
 	flannelFlags.StringVar(&opts.publicIPv6, "public-ipv6", "", "IPv6 accessible by other nodes for inter-host communication")
 	flannelFlags.StringVar(&opts.publicIPv6, "public-ipv6", "", "IPv6 accessible by other nodes for inter-host communication")
 	flannelFlags.IntVar(&opts.subnetLeaseRenewMargin, "subnet-lease-renew-margin", 60, "subnet lease renewal margin, in minutes, ranging from 1 to 1439")
 	flannelFlags.IntVar(&opts.subnetLeaseRenewMargin, "subnet-lease-renew-margin", 60, "subnet lease renewal margin, in minutes, ranging from 1 to 1439")
 	flannelFlags.BoolVar(&opts.ipMasq, "ip-masq", false, "setup IP masquerade rule for traffic destined outside of overlay network")
 	flannelFlags.BoolVar(&opts.ipMasq, "ip-masq", false, "setup IP masquerade rule for traffic destined outside of overlay network")
-	flannelFlags.BoolVar(&opts.autoDetectIPv4, "auto-detect-ipv4", true, "auto detect ipv4 address of the iface")
-	flannelFlags.BoolVar(&opts.autoDetectIPv6, "auto-detect-ipv6", false, "auto detect ipv6 address of the iface")
 	flannelFlags.BoolVar(&opts.kubeSubnetMgr, "kube-subnet-mgr", false, "contact the Kubernetes API for subnet assignment instead of etcd.")
 	flannelFlags.BoolVar(&opts.kubeSubnetMgr, "kube-subnet-mgr", false, "contact the Kubernetes API for subnet assignment instead of etcd.")
 	flannelFlags.StringVar(&opts.kubeApiUrl, "kube-api-url", "", "Kubernetes API server URL. Does not need to be specified if flannel is running in a pod.")
 	flannelFlags.StringVar(&opts.kubeApiUrl, "kube-api-url", "", "Kubernetes API server URL. Does not need to be specified if flannel is running in a pod.")
 	flannelFlags.StringVar(&opts.kubeAnnotationPrefix, "kube-annotation-prefix", "flannel.alpha.coreos.com", `Kubernetes annotation prefix. Can contain single slash "/", otherwise it will be appended at the end.`)
 	flannelFlags.StringVar(&opts.kubeAnnotationPrefix, "kube-annotation-prefix", "flannel.alpha.coreos.com", `Kubernetes annotation prefix. Can contain single slash "/", otherwise it will be appended at the end.`)
@@ -225,15 +223,53 @@ func main() {
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 
 
+	// This is the main context that everything should run in.
+	// All spawned goroutines should exit when cancel is called on this context.
+	// Go routines spawned from main.go coordinate using a WaitGroup. This provides a mechanism to allow the shutdownHandler goroutine
+	// to block until all the goroutines return . If those goroutines spawn other goroutines then they are responsible for
+	// blocking and returning only when cancel() is called.
+	ctx, cancel := context.WithCancel(context.Background())
+
+	sm, err := newSubnetManager(ctx)
+	if err != nil {
+		log.Error("Failed to create SubnetManager: ", err)
+		os.Exit(1)
+	}
+	log.Infof("Created subnet manager: %s", sm.Name())
+
+	// Register for SIGINT and SIGTERM
+	log.Info("Installing signal handlers")
+	sigs := make(chan os.Signal, 1)
+	signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
+
+	wg := sync.WaitGroup{}
+
+	wg.Add(1)
+	go func() {
+		shutdownHandler(ctx, sigs, cancel)
+		wg.Done()
+	}()
+
+	if opts.healthzPort > 0 {
+		// It's not super easy to shutdown the HTTP server so don't attempt to stop it cleanly
+		go mustRunHealthz()
+	}
+
+	// Fetch the network config (i.e. what backend to use etc..).
+	config, err := getConfig(ctx, sm)
+	if err == errCanceled {
+		wg.Wait()
+		os.Exit(0)
+	}
+
 	// Get ip family stack
 	// Get ip family stack
-	ipStack, stackErr := getIPFamily(opts.autoDetectIPv4, opts.autoDetectIPv6)
+	ipStack, stackErr := getIPFamily(config.EnableIPv4, config.EnableIPv6)
 	if stackErr != nil {
 	if stackErr != nil {
 		log.Error(stackErr.Error())
 		log.Error(stackErr.Error())
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 	// Work out which interface to use
 	// Work out which interface to use
 	var extIface *backend.ExternalInterface
 	var extIface *backend.ExternalInterface
-	var err error
 	// Check the default interface only if no interfaces are specified
 	// Check the default interface only if no interfaces are specified
 	if len(opts.iface) == 0 && len(opts.ifaceRegex) == 0 {
 	if len(opts.iface) == 0 && len(opts.ifaceRegex) == 0 {
 		extIface, err = LookupExtIface(opts.publicIP, "", ipStack)
 		extIface, err = LookupExtIface(opts.publicIP, "", ipStack)
@@ -275,44 +311,7 @@ func main() {
 		}
 		}
 	}
 	}
 
 
-	// This is the main context that everything should run in.
-	// All spawned goroutines should exit when cancel is called on this context.
-	// Go routines spawned from main.go coordinate using a WaitGroup. This provides a mechanism to allow the shutdownHandler goroutine
-	// to block until all the goroutines return . If those goroutines spawn other goroutines then they are responsible for
-	// blocking and returning only when cancel() is called.
-	ctx, cancel := context.WithCancel(context.Background())
-
-	sm, err := newSubnetManager(ctx)
-	if err != nil {
-		log.Error("Failed to create SubnetManager: ", err)
-		os.Exit(1)
-	}
-	log.Infof("Created subnet manager: %s", sm.Name())
-
-	// Register for SIGINT and SIGTERM
-	log.Info("Installing signal handlers")
-	sigs := make(chan os.Signal, 1)
-	signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
-
-	wg := sync.WaitGroup{}
 
 
-	wg.Add(1)
-	go func() {
-		shutdownHandler(ctx, sigs, cancel)
-		wg.Done()
-	}()
-
-	if opts.healthzPort > 0 {
-		// It's not super easy to shutdown the HTTP server so don't attempt to stop it cleanly
-		go mustRunHealthz()
-	}
-
-	// Fetch the network config (i.e. what backend to use etc..).
-	config, err := getConfig(ctx, sm)
-	if err == errCanceled {
-		wg.Wait()
-		os.Exit(0)
-	}
 
 
 	// Create a backend manager then use it to create the backend and register the network with it.
 	// Create a backend manager then use it to create the backend and register the network with it.
 	bm := backend.NewManager(ctx, sm, extIface)
 	bm := backend.NewManager(ctx, sm, extIface)
@@ -517,12 +516,17 @@ func MonitorLease(ctx context.Context, sm subnet.Manager, bn backend.Network, wg
 	}
 	}
 }
 }
 
 
-func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.ExternalInterface, error) {
+func LookupExtIface(ifname string, ifregexS string, ipStack int) (*backend.ExternalInterface, error) {
 	var iface *net.Interface
 	var iface *net.Interface
 	var ifaceAddr net.IP
 	var ifaceAddr net.IP
 	var ifaceV6Addr net.IP
 	var ifaceV6Addr net.IP
 	var err error
 	var err error
 
 
+        ifregex, err := regexp.Compile(ifregexS)
+	if err != nil {
+		return nil, fmt.Errorf("could not compile the IP address regex '%s': %w", ifregexS, err)
+	}
+
 	// Check ip family stack
 	// Check ip family stack
 	if ipStack == noneStack {
 	if ipStack == noneStack {
 		return nil, fmt.Errorf("none matched ip stack")
 		return nil, fmt.Errorf("none matched ip stack")
@@ -578,9 +582,9 @@ func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.Extern
 					continue
 					continue
 				}
 				}
 
 
-				matched, err := regexp.MatchString(ifregex, ifaceIP.String())
+				matched, err := ifregex.MatchString(ifaceIP.String())
 				if err != nil {
 				if err != nil {
-					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregex, ifaceIP.String())
+					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregexS, ifaceIP.String())
 				}
 				}
 
 
 				if matched {
 				if matched {
@@ -595,9 +599,9 @@ func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.Extern
 					continue
 					continue
 				}
 				}
 
 
-				matched, err := regexp.MatchString(ifregex, ifaceIP.String())
+				matched, err := ifregex.MatchString(ifaceIP.String())
 				if err != nil {
 				if err != nil {
-					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregex, ifaceIP.String())
+					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregexS, ifaceIP.String())
 				}
 				}
 
 
 				if matched {
 				if matched {
@@ -612,9 +616,9 @@ func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.Extern
 					continue
 					continue
 				}
 				}
 
 
-				matched, err := regexp.MatchString(ifregex, ifaceIP.String())
+				matched, err := ifregex.MatchString(ifaceIP.String())
 				if err != nil {
 				if err != nil {
-					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregex, ifaceIP.String())
+					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregexS, ifaceIP.String())
 				}
 				}
 
 
 				ifaceV6IP, err := ip.GetInterfaceIP6Addr(&ifaceToMatch)
 				ifaceV6IP, err := ip.GetInterfaceIP6Addr(&ifaceToMatch)
@@ -623,9 +627,9 @@ func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.Extern
 					continue
 					continue
 				}
 				}
 
 
-				v6Matched, err := regexp.MatchString(ifregex, ifaceV6IP.String())
+				v6Matched, err := ifregex.MatchString(ifaceV6IP.String())
 				if err != nil {
 				if err != nil {
-					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregex, ifaceIP.String())
+					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregexS, ifaceIP.String())
 				}
 				}
 
 
 				if matched && v6Matched {
 				if matched && v6Matched {
@@ -640,9 +644,9 @@ func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.Extern
 		// Check Name
 		// Check Name
 		if iface == nil && (ifaceAddr == nil || ifaceV6Addr == nil) {
 		if iface == nil && (ifaceAddr == nil || ifaceV6Addr == nil) {
 			for _, ifaceToMatch := range ifaces {
 			for _, ifaceToMatch := range ifaces {
-				matched, err := regexp.MatchString(ifregex, ifaceToMatch.Name)
+				matched, err := ifregex.MatchString(ifaceToMatch.Name)
 				if err != nil {
 				if err != nil {
-					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregex, ifaceToMatch.Name)
+					return nil, fmt.Errorf("regex error matching pattern %s to %s", ifregexS, ifaceToMatch.Name)
 				}
 				}
 
 
 				if matched {
 				if matched {
@@ -666,26 +670,26 @@ func LookupExtIface(ifname string, ifregex string, ipStack int) (*backend.Extern
 				availableFaces = append(availableFaces, fmt.Sprintf("%s:%s", f.Name, ipaddr))
 				availableFaces = append(availableFaces, fmt.Sprintf("%s:%s", f.Name, ipaddr))
 			}
 			}
 
 
-			return nil, fmt.Errorf("Could not match pattern %s to any of the available network interfaces (%s)", ifregex, strings.Join(availableFaces, ", "))
+			return nil, fmt.Errorf("Could not match pattern %s to any of the available network interfaces (%s)", ifregexS, strings.Join(availableFaces, ", "))
 		}
 		}
 	} else {
 	} else {
 		log.Info("Determining IP address of default interface")
 		log.Info("Determining IP address of default interface")
 		switch ipStack {
 		switch ipStack {
 		case ipv4Stack:
 		case ipv4Stack:
 			if iface, err = ip.GetDefaultGatewayInterface(); err != nil {
 			if iface, err = ip.GetDefaultGatewayInterface(); err != nil {
-				return nil, fmt.Errorf("failed to get default interface: %s", err)
+				return nil, fmt.Errorf("failed to get default interface: %w", err)
 			}
 			}
 		case ipv6Stack:
 		case ipv6Stack:
 			if iface, err = ip.GetDefaultV6GatewayInterface(); err != nil {
 			if iface, err = ip.GetDefaultV6GatewayInterface(); err != nil {
-				return nil, fmt.Errorf("failed to get default v6 interface: %s", err)
+				return nil, fmt.Errorf("failed to get default v6 interface: %w", err)
 			}
 			}
 		case dualStack:
 		case dualStack:
 			if iface, err = ip.GetDefaultGatewayInterface(); err != nil {
 			if iface, err = ip.GetDefaultGatewayInterface(); err != nil {
-				return nil, fmt.Errorf("failed to get default interface: %s", err)
+				return nil, fmt.Errorf("failed to get default interface: %w", err)
 			}
 			}
 			v6Iface, err := ip.GetDefaultV6GatewayInterface()
 			v6Iface, err := ip.GetDefaultV6GatewayInterface()
 			if err != nil {
 			if err != nil {
-				return nil, fmt.Errorf("failed to get default v6 interface: %s", err)
+				return nil, fmt.Errorf("failed to get default v6 interface: %w", err)
 			}
 			}
 			if iface.Name != v6Iface.Name {
 			if iface.Name != v6Iface.Name {
 				return nil, fmt.Errorf("v6 default route interface %s "+
 				return nil, fmt.Errorf("v6 default route interface %s "+

+ 4 - 9
pkg/ip/iface.go

@@ -261,24 +261,19 @@ func EnsureV4AddressOnLink(ipa IP4Net, ipn IP4Net, link netlink.Link) error {
 
 
 // EnsureV6AddressOnLink ensures that there is only one v6 Addr on `link` and it equals `ipn`.
 // EnsureV6AddressOnLink ensures that there is only one v6 Addr on `link` and it equals `ipn`.
 // If there exist multiple addresses on link, it returns an error message to tell callers to remove additional address.
 // If there exist multiple addresses on link, it returns an error message to tell callers to remove additional address.
-func EnsureV6AddressOnLink(ipn IP6Net, link netlink.Link) error {
-	addr := netlink.Addr{IPNet: ipn.ToIPNet()}
+func EnsureV6AddressOnLink(ipa IP6Net, ipn IP6Net, link netlink.Link) error {
+	addr := netlink.Addr{IPNet: ipa.ToIPNet()}
 	existingAddrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
 	existingAddrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	// flannel will never make this happen. This situation can only be caused by a user, so get them to sort it out.
-	if len(existingAddrs) > 2 {
-		return fmt.Errorf("link has incompatible v6 addresses. Remove additional v6 addresses and try again. %#v", link)
-	}
-
 	onlyLinkLocal := true
 	onlyLinkLocal := true
 	for _, existingAddr := range existingAddrs {
 	for _, existingAddr := range existingAddrs {
 		if !existingAddr.IP.IsLinkLocalUnicast() {
 		if !existingAddr.IP.IsLinkLocalUnicast() {
 			if !existingAddr.Equal(addr) {
 			if !existingAddr.Equal(addr) {
 				if err := netlink.AddrDel(link, &existingAddr); err != nil {
 				if err := netlink.AddrDel(link, &existingAddr); err != nil {
-					return fmt.Errorf("failed to remove v6 IP address %s from %s: %s", ipn.String(), link.Attrs().Name, err)
+					return fmt.Errorf("failed to remove v6 IP address %s from %s: %w", ipn.String(), link.Attrs().Name, err)
 				}
 				}
 				existingAddrs = []netlink.Addr{}
 				existingAddrs = []netlink.Addr{}
 				onlyLinkLocal = false
 				onlyLinkLocal = false
@@ -295,7 +290,7 @@ func EnsureV6AddressOnLink(ipn IP6Net, link netlink.Link) error {
 	// Actually add the desired address to the interface if needed.
 	// Actually add the desired address to the interface if needed.
 	if len(existingAddrs) == 0 {
 	if len(existingAddrs) == 0 {
 		if err := netlink.AddrAdd(link, &addr); err != nil {
 		if err := netlink.AddrAdd(link, &addr); err != nil {
-			return fmt.Errorf("failed to add v6 IP address %s to %s: %s", ipn.String(), link.Attrs().Name, err)
+			return fmt.Errorf("failed to add v6 IP address %s to %s: %w", ipn.String(), link.Attrs().Name, err)
 		}
 		}
 	}
 	}
 
 

+ 14 - 5
pkg/ip/iface_test.go

@@ -74,7 +74,8 @@ func TestEnsureV6AddressOnLink(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	// check changing address
 	// check changing address
-	if err := EnsureV6AddressOnLink(IP6Net{IP: FromIP6(net.ParseIP("::2")), PrefixLen: 64}, lo); err != nil {
+	ipn := IP6Net{IP: FromIP6(net.ParseIP("::2")), PrefixLen: 64}
+	if err := EnsureV6AddressOnLink(ipn, ipn, lo); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	addrs, err := netlink.AddrList(lo, netlink.FAMILY_V6)
 	addrs, err := netlink.AddrList(lo, netlink.FAMILY_V6)
@@ -86,13 +87,21 @@ func TestEnsureV6AddressOnLink(t *testing.T) {
 	}
 	}
 
 
 	// check changing address if there exist multiple addresses
 	// check changing address if there exist multiple addresses
-	if err := netlink.AddrAdd(lo, &netlink.Addr{IPNet: &net.IPNet{IP: net.ParseIP("::3"), Mask: net.CIDRMask(64, 128)}}); err != nil {
+	if err := netlink.AddrAdd(lo, &netlink.Addr{IPNet: &net.IPNet{IP: net.ParseIP("2001::4"), Mask: net.CIDRMask(64, 128)}}); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	if err := netlink.AddrAdd(lo, &netlink.Addr{IPNet: &net.IPNet{IP: net.ParseIP("::4"), Mask: net.CIDRMask(64, 128)}}); err != nil {
+	addrs, err = netlink.AddrList(lo, netlink.FAMILY_V6)
+	if len(addrs) != 2 {
+		t.Fatalf("two addresses expected, addrs: %v", addrs)
+	}
+	if err := EnsureV6AddressOnLink(ipn, ipn, lo); err != nil {
+		t.Fatal(err)
+	}
+	addrs, err = netlink.AddrList(lo, netlink.FAMILY_V6)
+	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	if err := EnsureV6AddressOnLink(IP6Net{IP: FromIP6(net.ParseIP("::2")), PrefixLen: 64}, lo); err == nil {
-		t.Fatal("EnsureV6AddressOnLink should return error if there exist thress address on link")
+	if len(addrs) != 1 {
+		t.Fatalf("only one address expected, addrs: %v", addrs)
 	}
 	}
 }
 }

+ 0 - 3
subnet/config.go

@@ -60,9 +60,6 @@ func ParseConfig(s string) (*Config, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	if !cfg.EnableIPv4 && !cfg.EnableIPv6 {
-		return nil, fmt.Errorf("EnableIPv4 or EnableIPv6 option must be enabled one at least")
-	}
 
 
 	if cfg.EnableIPv4 {
 	if cfg.EnableIPv4 {
 		if cfg.SubnetLen > 0 {
 		if cfg.SubnetLen > 0 {

+ 4 - 8
subnet/etcdv2/local_manager.go

@@ -103,8 +103,7 @@ func (m *LocalManager) AcquireLease(ctx context.Context, attrs *LeaseAttrs) (*Le
 		l, err := m.tryAcquireLease(ctx, config, attrs.PublicIP, attrs)
 		l, err := m.tryAcquireLease(ctx, config, attrs.PublicIP, attrs)
 		switch err {
 		switch err {
 		case nil:
 		case nil:
-			//TODO - temporarily compatible with dual stack,
-			// only vxlan backend and kube subnet manager support dual stack now.
+			//TODO only vxlan backend and kube subnet manager support dual stack now.
 			l.EnableIPv4 = true
 			l.EnableIPv4 = true
 			l.EnableIPv6 = false
 			l.EnableIPv6 = false
 			return l, nil
 			return l, nil
@@ -292,8 +291,7 @@ func (m *LocalManager) leaseWatchReset(ctx context.Context, sn ip.IP4Net) (Lease
 		return LeaseWatchResult{}, err
 		return LeaseWatchResult{}, err
 	}
 	}
 
 
-	//TODO - temporarily compatible with dual stack,
-	// only vxlan backend and kube subnet manager support dual stack now.
+	//TODO only vxlan backend and kube subnet manager support dual stack now.
 	l.EnableIPv4 = true
 	l.EnableIPv4 = true
 	l.EnableIPv6 = false
 	l.EnableIPv6 = false
 
 
@@ -317,8 +315,7 @@ func (m *LocalManager) WatchLease(ctx context.Context, sn ip.IP4Net, cursor inte
 
 
 	switch {
 	switch {
 	case err == nil:
 	case err == nil:
-		//TODO - temporarily compatible with dual stack,
-		// only vxlan backend and kube subnet manager support dual stack now.
+		//TODO only vxlan backend and kube subnet manager support dual stack now.
 		evt.Lease.EnableIPv4 = true
 		evt.Lease.EnableIPv4 = true
 		evt.Lease.EnableIPv6 = false
 		evt.Lease.EnableIPv6 = false
 		return LeaseWatchResult{
 		return LeaseWatchResult{
@@ -348,8 +345,7 @@ func (m *LocalManager) WatchLeases(ctx context.Context, cursor interface{}) (Lea
 	evt, index, err := m.registry.watchSubnets(ctx, nextIndex)
 	evt, index, err := m.registry.watchSubnets(ctx, nextIndex)
 	switch {
 	switch {
 	case err == nil:
 	case err == nil:
-		//TODO - temporarily compatible with dual stack,
-		// only vxlan backend and kube subnet manager support dual stack now.
+		//TODO only vxlan backend and kube subnet manager support dual stack now.
 		evt.Lease.EnableIPv4 = true
 		evt.Lease.EnableIPv4 = true
 		evt.Lease.EnableIPv6 = false
 		evt.Lease.EnableIPv6 = false
 		return LeaseWatchResult{
 		return LeaseWatchResult{

+ 1 - 2
subnet/etcdv2/registry.go

@@ -314,8 +314,7 @@ func nodeToLease(node *etcd.Node) (*Lease, error) {
 	}
 	}
 
 
 	lease := Lease{
 	lease := Lease{
-		//TODO - temporarily compatible with dual stack,
-		// only vxlan backend and kube subnet manager support dual stack now.
+		//TODO only vxlan backend and kube subnet manager support dual stack now.
 		EnableIPv4: true,
 		EnableIPv4: true,
 		EnableIPv6: false,
 		EnableIPv6: false,
 		Subnet:     *sn,
 		Subnet:     *sn,

+ 2 - 4
subnet/kube/kube.go

@@ -284,8 +284,7 @@ func (ksm *kubeSubnetManager) AcquireLease(ctx context.Context, attrs *subnet.Le
 			(n.Annotations[ksm.annotations.BackendPublicIPv6Overwrite] != "" && n.Annotations[ksm.annotations.BackendPublicIPv6Overwrite] != attrs.PublicIPv6.String())) {
 			(n.Annotations[ksm.annotations.BackendPublicIPv6Overwrite] != "" && n.Annotations[ksm.annotations.BackendPublicIPv6Overwrite] != attrs.PublicIPv6.String())) {
 		n.Annotations[ksm.annotations.BackendType] = attrs.BackendType
 		n.Annotations[ksm.annotations.BackendType] = attrs.BackendType
 
 
-		//TODO - temporarily compatible with dual stack,
-		// only vxlan backend support dual stack now.
+		//TODO -i only vxlan backend support dual stack now.
 		if (attrs.BackendType == "vxlan" && string(bd) != "null") || attrs.BackendType != "vxlan" {
 		if (attrs.BackendType == "vxlan" && string(bd) != "null") || attrs.BackendType != "vxlan" {
 			n.Annotations[ksm.annotations.BackendData] = string(bd)
 			n.Annotations[ksm.annotations.BackendData] = string(bd)
 			if n.Annotations[ksm.annotations.BackendPublicIPOverwrite] != "" {
 			if n.Annotations[ksm.annotations.BackendPublicIPOverwrite] != "" {
@@ -355,8 +354,7 @@ func (ksm *kubeSubnetManager) AcquireLease(ctx context.Context, attrs *subnet.Le
 	if ipv6Cidr != nil {
 	if ipv6Cidr != nil {
 		lease.IPv6Subnet = ip.FromIP6Net(ipv6Cidr)
 		lease.IPv6Subnet = ip.FromIP6Net(ipv6Cidr)
 	}
 	}
-	//TODO - temporarily compatible with dual stack,
-	// only vxlan backend support dual stack now.
+	//TODO - only vxlan backend support dual stack now.
 	if attrs.BackendType != "vxlan" {
 	if attrs.BackendType != "vxlan" {
 		lease.EnableIPv4 = true
 		lease.EnableIPv4 = true
 		lease.EnableIPv6 = false
 		lease.EnableIPv6 = false

+ 6 - 6
subnet/watch.go

@@ -88,7 +88,7 @@ func (lw *leaseWatcher) reset(leases []Lease) []Event {
 			continue
 			continue
 		} else if lw.ownLease != nil && !nl.EnableIPv4 && !nl.EnableIPv6 &&
 		} else if lw.ownLease != nil && !nl.EnableIPv4 && !nl.EnableIPv6 &&
 			nl.Subnet.Equal(lw.ownLease.Subnet) {
 			nl.Subnet.Equal(lw.ownLease.Subnet) {
-			//TODO - temporarily compatible with etcd subnet manager
+			//TODO - dual-stack temporarily only compatible with kube subnet manager
 			continue
 			continue
 		}
 		}
 
 
@@ -108,7 +108,7 @@ func (lw *leaseWatcher) reset(leases []Lease) []Event {
 				found = true
 				found = true
 				break
 				break
 			} else if !ol.EnableIPv4 && !ol.EnableIPv6 && ol.Subnet.Equal(nl.Subnet) {
 			} else if !ol.EnableIPv4 && !ol.EnableIPv6 && ol.Subnet.Equal(nl.Subnet) {
-				//TODO - temporarily compatible with etcd subnet manager
+				//TODO - dual-stack temporarily only compatible with kube subnet manager
 				lw.leases = deleteLease(lw.leases, i)
 				lw.leases = deleteLease(lw.leases, i)
 				found = true
 				found = true
 				break
 				break
@@ -135,7 +135,7 @@ func (lw *leaseWatcher) reset(leases []Lease) []Event {
 			continue
 			continue
 		} else if lw.ownLease != nil && !l.EnableIPv4 && !l.EnableIPv6 &&
 		} else if lw.ownLease != nil && !l.EnableIPv4 && !l.EnableIPv6 &&
 			l.Subnet.Equal(lw.ownLease.Subnet) {
 			l.Subnet.Equal(lw.ownLease.Subnet) {
-			//TODO - temporarily compatible with etcd subnet manager
+			//TODO - dual-stack temporarily only compatible with kube subnet manager
 			continue
 			continue
 		}
 		}
 		batch = append(batch, Event{EventRemoved, l})
 		batch = append(batch, Event{EventRemoved, l})
@@ -164,7 +164,7 @@ func (lw *leaseWatcher) update(events []Event) []Event {
 			continue
 			continue
 		} else if lw.ownLease != nil && !e.Lease.EnableIPv4 && !e.Lease.EnableIPv6 &&
 		} else if lw.ownLease != nil && !e.Lease.EnableIPv4 && !e.Lease.EnableIPv6 &&
 			e.Lease.Subnet.Equal(lw.ownLease.Subnet) {
 			e.Lease.Subnet.Equal(lw.ownLease.Subnet) {
-			//TODO - temporarily compatible with etcd subnet manager
+			//TODO - dual-stack temporarily only compatible with kube subnet manager
 			continue
 			continue
 		}
 		}
 
 
@@ -193,7 +193,7 @@ func (lw *leaseWatcher) add(lease *Lease) Event {
 			lw.leases[i] = *lease
 			lw.leases[i] = *lease
 			return Event{EventAdded, lw.leases[i]}
 			return Event{EventAdded, lw.leases[i]}
 		} else if !l.EnableIPv4 && !l.EnableIPv6 && l.Subnet.Equal(lease.Subnet) {
 		} else if !l.EnableIPv4 && !l.EnableIPv6 && l.Subnet.Equal(lease.Subnet) {
-			//TODO - temporarily compatible with etcd subnet manager
+			//TODO - dual-stack temporarily only compatible with kube subnet manager
 			lw.leases[i] = *lease
 			lw.leases[i] = *lease
 			return Event{EventAdded, lw.leases[i]}
 			return Event{EventAdded, lw.leases[i]}
 		}
 		}
@@ -216,7 +216,7 @@ func (lw *leaseWatcher) remove(lease *Lease) Event {
 			lw.leases = deleteLease(lw.leases, i)
 			lw.leases = deleteLease(lw.leases, i)
 			return Event{EventRemoved, l}
 			return Event{EventRemoved, l}
 		} else if !l.EnableIPv4 && !l.EnableIPv6 && l.Subnet.Equal(lease.Subnet) {
 		} else if !l.EnableIPv4 && !l.EnableIPv6 && l.Subnet.Equal(lease.Subnet) {
-			//TODO - temporarily compatible with etcd subnet manager
+			//TODO - dual-stack temporarily only compatible with kube subnet manager
 			lw.leases = deleteLease(lw.leases, i)
 			lw.leases = deleteLease(lw.leases, i)
 			return Event{EventRemoved, l}
 			return Event{EventRemoved, l}
 		}
 		}