//go:build linux package macvlan import ( "context" "fmt" "net" "github.com/containerd/log" "github.com/moby/moby/v2/daemon/libnetwork/driverapi" "github.com/moby/moby/v2/daemon/libnetwork/netlabel" "github.com/moby/moby/v2/daemon/libnetwork/netutils" "github.com/moby/moby/v2/daemon/libnetwork/ns" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // Join method is invoked when a Sandbox is attached to an endpoint. func (d *driver) Join(ctx context.Context, nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, epOpts, _ map[string]any) error { ctx, span := otel.Tracer("").Start(ctx, "libnetwork.drivers.macvlan.Join", trace.WithAttributes( attribute.String("nid", nid), attribute.String("eid", eid), attribute.String("sboxKey", sboxKey))) defer span.End() n, err := d.getNetwork(nid) if err != nil { return err } // generate a name for the iface that will be renamed to eth0 in the sbox containerIfName, err := netutils.GenerateIfaceName(ns.NlHandle(), vethPrefix, vethLen) if err != nil { return fmt.Errorf("error generating an interface name: %w", err) } // create the netlink macvlan interface vethName, err := createMacVlan(containerIfName, n.config.Parent, n.config.MacvlanMode) if err != nil { return err } ep, err := n.endpoint(eid) if err != nil { return err } // bind the generated iface name to the endpoint // // TODO(thaJeztah): this should really be done under a lock. ep.srcName = vethName if !n.config.Internal { // parse and correlate the endpoint v4 address with the available v4 subnets if ep.addr != nil && len(n.config.Ipv4Subnets) > 0 { s := getSubnetForIP(ep.addr, n.config.Ipv4Subnets) if s == nil { return fmt.Errorf("could not find a valid ipv4 subnet for endpoint %s", eid) } if s.GwIP == "" { // Can't set up a default gateway, but the network is not internal, so it should // be treated as having external connectivity. (This preserves old behavior, // where a gateway address was assigned from IPAM that did not necessarily // correspond with a working gateway.) jinfo.ForceGw4() } else { v4gw, _, err := net.ParseCIDR(s.GwIP) if err != nil { return fmt.Errorf("gateway %s is not a valid ipv4 address: %v", s.GwIP, err) } err = jinfo.SetGateway(v4gw) if err != nil { return err } } log.G(ctx).Debugf("Macvlan Endpoint Joined with IPv4_Addr: %s, Gateway: %s, MacVlan_Mode: %s, Parent: %s", ep.addr.IP.String(), s.GwIP, n.config.MacvlanMode, n.config.Parent) } // parse and correlate the endpoint v6 address with the available v6 subnets if ep.addrv6 != nil && len(n.config.Ipv6Subnets) > 0 { s := getSubnetForIP(ep.addrv6, n.config.Ipv6Subnets) if s == nil { return fmt.Errorf("could not find a valid ipv6 subnet for endpoint %s", eid) } if s.GwIP == "" { // Can't set up a default gateway, but the network is not internal, so it should // be treated as having external connectivity. (This preserves old behavior, // where a gateway address was assigned from IPAM that did not necessarily // correspond with a working gateway.) jinfo.ForceGw6() } else { v6gw, _, err := net.ParseCIDR(s.GwIP) if err != nil { return fmt.Errorf("gateway %s is not a valid ipv6 address: %v", s.GwIP, err) } err = jinfo.SetGatewayIPv6(v6gw) if err != nil { return err } } log.G(ctx).Debugf("Macvlan Endpoint Joined with IPv6_Addr: %s Gateway: %s MacVlan_Mode: %s, Parent: %s", ep.addrv6.IP.String(), s.GwIP, n.config.MacvlanMode, n.config.Parent) } } else { if len(n.config.Ipv4Subnets) > 0 { log.G(ctx).Debugf("Macvlan Endpoint Joined with IPv4_Addr: %s, MacVlan_Mode: %s, Parent: %s", ep.addr.IP.String(), n.config.MacvlanMode, n.config.Parent) } if len(n.config.Ipv6Subnets) > 0 { log.G(ctx).Debugf("Macvlan Endpoint Joined with IPv6_Addr: %s MacVlan_Mode: %s, Parent: %s", ep.addrv6.IP.String(), n.config.MacvlanMode, n.config.Parent) } } jinfo.DisableGatewayService() iNames := jinfo.InterfaceName() err = iNames.SetNames(vethName, containerVethPrefix, netlabel.GetIfname(epOpts)) if err != nil { return err } if err := d.storeUpdate(ep); err != nil { return fmt.Errorf("failed to save macvlan endpoint %.7s to store: %v", ep.id, err) } return nil } // Leave method is invoked when a Sandbox detaches from an endpoint. func (d *driver) Leave(nid, eid string) error { return nil } // getSubnetForIP returns the (IPv4 or IPv6) subnet to which the given IP belongs. func getSubnetForIP(ip *net.IPNet, subnets []*ipSubnet) *ipSubnet { for _, s := range subnets { _, snet, err := net.ParseCIDR(s.SubnetIP) if err != nil { return nil } // first check if the mask lengths are the same i, _ := snet.Mask.Size() j, _ := ip.Mask.Size() if i != j { continue } if snet.Contains(ip.IP) { return s } } return nil }