From 960feef32481cda048e53d97cd6cc061a2bb03e3 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 2 Mar 2016 09:53:11 -0600 Subject: [PATCH] spec/plugins: pass interface names and MACs back to runtime Allows the runtime to leave interface naming decisions up to the plugin and for the plugin to pass interface details back to the runtime which can be useful for further runtime operations. --- SPEC.md | 18 ++++++- pkg/ip/link.go | 78 ++++++++++++++++++++++++---- pkg/types/types.go | 18 +++++-- plugins/main/bridge/bridge.go | 25 +++++---- plugins/main/bridge/bridge_test.go | 19 ++++--- plugins/main/ipvlan/ipvlan.go | 44 +++++++++------- plugins/main/ipvlan/ipvlan_test.go | 8 ++- plugins/main/macvlan/macvlan.go | 44 +++++++++------- plugins/main/macvlan/macvlan_test.go | 8 ++- plugins/main/ptp/ptp.go | 26 ++++++---- 10 files changed, 207 insertions(+), 81 deletions(-) diff --git a/SPEC.md b/SPEC.md index f71e7a6bd..bb7671bbf 100644 --- a/SPEC.md +++ b/SPEC.md @@ -49,6 +49,7 @@ The operations that the CNI plugin needs to support are: - **Extra arguments**. This allows granular configuration of CNI plugins on a per-container basis. - **Name of the interface inside the container**. This is the name that should be assigned to the interface created inside the container (network namespace); consequently it must comply with the standard Linux restrictions on interface names. - Result: + - **Container interface details**. Depending on the plugin, this can include the container interface name and/or the host interface name, and the hardware addresses of both interfaces. - **IPs assigned to the interface**. This is either an IPv4 address, an IPv6 address, or both. - **DNS information**. Dictionary that includes DNS information for nameservers, domain, search domains and options. @@ -67,7 +68,7 @@ It will then look for this executable in a list of predefined directories. Once - `CNI_COMMAND`: indicates the desired operation; either `ADD` or `DEL` - `CNI_CONTAINERID`: Container ID - `CNI_NETNS`: Path to network namespace file -- `CNI_IFNAME`: Interface name to set up +- `CNI_IFNAME`: Interface name to set up; plugin must honor this interface name or return an error - `CNI_ARGS`: Extra arguments passed in by the user at invocation time. Alphanumeric key-value pairs separated by semicolons; for example, "FOO=BAR;ABC=123" - `CNI_PATH`: Colon-separated list of paths to search for CNI plugin executables @@ -76,11 +77,17 @@ Network configuration in JSON format is streamed through stdin. ### Result -Success is indicated by a return code of zero and the following JSON printed to stdout in the case of the ADD command. This should be the same output as was returned by the IPAM plugin (see [IP Allocation](#ip-allocation) for details). +Success is indicated by a return code of zero and the following JSON printed to stdout in the case of the ADD command. The `ip4`, `ip6`, and `dns` items should be the same output as was returned by the IPAM plugin (see [IP Allocation](#ip-allocation) for details). ``` { "cniVersion": "0.1.0", + "interface": { + "hostIfname": , (optional) + "hostMac": , (optional) + "containerIfname": , (optional) + "containerMac": (optional) + } "ip4": { "ip": , "gateway": , (optional) @@ -105,6 +112,13 @@ Success is indicated by a return code of zero and the following JSON printed to The result is returned in the same format as specified in the [configuration](#network-configuration). The specification does not declare how this information must be processed by CNI consumers. Examples include generating an `/etc/resolv.conf` file to be injected into the container filesystem or running a DNS forwarder on the host. +The `interface` block is optional for backwards compatibility but plugins are strongly recommended to return it. +If the plugin does not use L2 addressing and thus MAC addresses are not meaningful, then `hostMac` and `containerMac` can be omitted. +`containerIfname` must always be returned if the `interface` block is returned. +`hostIfname` should be returned if the container is connected with an interface that provides two endpoints, like the Linux veth type. +Other types with only one endpoint, like macvlan or ipvlan, do not require returning `hostIfname`. +If the `CNI_IFNAME` variable is passed to the plugin, it must honor that name or return an error if it cannot. +If the `CNI_IFNAME` variable is omitted, the plugin should return the `interface` block so that the caller knows what interface name was created by the plugin inside the container. Errors are indicated by a non-zero return code and the following JSON being printed to stdout: ``` diff --git a/pkg/ip/link.go b/pkg/ip/link.go index 9c710265c..d42154d67 100644 --- a/pkg/ip/link.go +++ b/pkg/ip/link.go @@ -19,6 +19,7 @@ import ( "fmt" "net" "os" + "syscall" . "github.com/appc/cni/pkg/ops" "github.com/vishvananda/netlink" @@ -40,9 +41,15 @@ func makeVethPair(name, peer string, mtu int) (netlink.Link, error) { return veth, nil } -func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) { +func makeVeth(mtu int) (peerName string, veth netlink.Link, err error) { for i := 0; i < 10; i++ { - peerName, err = RandomVethName() + var name string + + peerName, err = RandomIfaceName("veth") + if err != nil { + return + } + name, err = RandomIfaceName("veth") if err != nil { return } @@ -66,25 +73,74 @@ func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err err return } -// RandomVethName returns string "veth" with random prefix (hashed from entropy) -func RandomVethName() (string, error) { - entropy := make([]byte, 4) +const ENTROPY_LEN = 4 + +// RandomIfaceName returns string @prefix with random suffix (hashed from entropy) +func RandomIfaceName(prefix string) (string, error) { + if len(prefix)+ENTROPY_LEN > syscall.IFNAMSIZ { + return "", fmt.Errorf("prefix %s too long", len(prefix)) + } + entropy := make([]byte, ENTROPY_LEN) _, err := rand.Reader.Read(entropy) if err != nil { return "", fmt.Errorf("failed to generate random veth name: %v", err) } - // NetworkManager (recent versions) will ignore veth devices that start with "veth" - return fmt.Sprintf("veth%x", entropy), nil + // NetworkManager (1.0+) will ignore externally created virtual interfaces + return fmt.Sprintf("%s%x", prefix, entropy), nil +} + +func RenameLink(curName, newName, newPrefix string) (string, error) { + if len(newName) == 0 && len(newPrefix) == 0 { + return "", fmt.Errorf("one of new name or new name prefix required") + } + + link, err := Net().LinkByName(curName) + if err != nil { + return "", err + } + + if len(newName) > 0 { + if err := Net().LinkSetName(link, newName); err != nil { + return "", err + } + return newName, nil + } + + for idx := 0; idx < 100; idx++ { + newName = fmt.Sprintf(newPrefix, idx) + err = Net().LinkSetName(link, newName) + if err == nil { + return newName, nil + } else if !os.IsExist(err) { + return "", err + } + } + return "", err } // SetupVeth sets up a virtual ethernet link. // Should be in container netns, and will switch back to hostNS to set the host -// veth end up. -func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) { - var hostVethName string - hostVethName, contVeth, err = makeVeth(contVethName, mtu) +// veth end up. If @contVethName is given the container interface will have +// that name or an error will be returned. If instead @contVethPrefix is +// given the container interface name will be constructed from that prefix +// and will be a unique name in the namespace. +func SetupVeth(contVethName, contVethPrefix string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) { + var hostVethName, newName string + hostVethName, contVeth, err = makeVeth(mtu) + if err != nil { + return + } + newName, err = RenameLink(contVeth.Attrs().Name, contVethName, contVethPrefix) + if err != nil { + return + } + contVethName = newName + + // Re-fetch container veth to get all properties/attributes + contVeth, err = Net().LinkByName(contVethName) if err != nil { + err = fmt.Errorf("failed to lookup %q: %v", contVethName, err) return } diff --git a/pkg/types/types.go b/pkg/types/types.go index 6948dcb1f..6b9244ba2 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -67,9 +67,10 @@ type NetConf struct { // Result is what gets returned from the plugin (via stdout) to the caller type Result struct { - IP4 *IPConfig `json:"ip4,omitempty"` - IP6 *IPConfig `json:"ip6,omitempty"` - DNS DNS `json:"dns,omitempty"` + Interface *InterfaceConfig `json:"interface,omitempty"` + IP4 *IPConfig `json:"ip4,omitempty"` + IP6 *IPConfig `json:"ip6,omitempty"` + DNS DNS `json:"dns,omitempty"` } func (r *Result) Print() error { @@ -87,6 +88,9 @@ func (r *Result) String() string { if r.IP6 != nil { str += fmt.Sprintf("IP6:%+v, ", *r.IP6) } + if r.Interface != nil { + str += fmt.Sprintf("Interface:%+v, ", *r.Interface) + } return fmt.Sprintf("%sDNS:%+v", str, r.DNS) } @@ -105,6 +109,14 @@ type DNS struct { Options []string `json:"options,omitempty"` } +// InterfaceConfig contains values about the created interfaces +type InterfaceConfig struct { + HostIfname string `json:"hostIfname,omitempty"` + HostMac string `json:"hostMac,omitempty"` + ContainerIfname string `json:"containerIfname,omitempty"` + ContainerMac string `json:"containerMac,omitempty"` +} + type Route struct { Dst net.IPNet GW net.IP diff --git a/plugins/main/bridge/bridge.go b/plugins/main/bridge/bridge.go index 4ff967145..5f5285f58 100644 --- a/plugins/main/bridge/bridge.go +++ b/plugins/main/bridge/bridge.go @@ -122,35 +122,38 @@ func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) { return br, nil } -func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error { - var hostVethName string +func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) (*types.InterfaceConfig, error) { + ifaceConfig := &types.InterfaceConfig{} err := Net().WithNetNSPath(netns, false, func(hostNS *os.File) error { // create the veth pair in the container and move host end into host netns - hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) + hostVeth, containerVeth, err := ip.SetupVeth(ifName, "eth%d", mtu, hostNS) if err != nil { return err } - hostVethName = hostVeth.Attrs().Name + ifaceConfig.ContainerIfname = containerVeth.Attrs().Name + ifaceConfig.ContainerMac = containerVeth.Attrs().HardwareAddr.String() + ifaceConfig.HostIfname = hostVeth.Attrs().Name return nil }) if err != nil { - return err + return nil, err } // need to lookup hostVeth again as its index has changed during ns move - hostVeth, err := Net().LinkByName(hostVethName) + hostVeth, err := Net().LinkByName(ifaceConfig.HostIfname) if err != nil { - return fmt.Errorf("failed to lookup %q: %v", hostVethName, err) + return nil, fmt.Errorf("failed to lookup %q: %v", ifaceConfig.HostIfname, err) } + ifaceConfig.HostMac = hostVeth.Attrs().HardwareAddr.String() // connect host veth end to the bridge if err = Net().LinkSetMaster(hostVeth, br); err != nil { - return fmt.Errorf("failed to connect %q to bridge %v: %v", hostVethName, br.Attrs().Name, err) + return nil, fmt.Errorf("failed to connect %q to bridge %v: %v", hostVeth.Attrs().Name, br.Attrs().Name, err) } - return nil + return ifaceConfig, nil } func calcGatewayIP(ipn *net.IPNet) net.IP { @@ -179,7 +182,8 @@ func cmdAdd(args *skel.CmdArgs) error { return err } - if err = setupVeth(args.Netns, br, args.IfName, n.MTU); err != nil { + ifaceConfig, err := setupVeth(args.Netns, br, args.IfName, n.MTU) + if err != nil { return err } @@ -227,6 +231,7 @@ func cmdAdd(args *skel.CmdArgs) error { } result.DNS = n.DNS + result.Interface = ifaceConfig return result.Print() } diff --git a/plugins/main/bridge/bridge_test.go b/plugins/main/bridge/bridge_test.go index af878d5fd..e5ac660b0 100644 --- a/plugins/main/bridge/bridge_test.go +++ b/plugins/main/bridge/bridge_test.go @@ -159,6 +159,7 @@ var _ = Describe("bridge Operations", func() { result := types.Result{} err = json.Unmarshal(out, &result) Expect(err).NotTo(HaveOccurred()) + Expect(result.Interface.ContainerIfname).To(Equal(IFNAME)) // Make sure bridge link exists link, err := tops.LinkByName(BRNAME) @@ -184,13 +185,11 @@ var _ = Describe("bridge Operations", func() { links, err := tops.LinkList() Expect(err).NotTo(HaveOccurred()) Expect(len(links)).To(Equal(2)) - for _, l := range links { - if l.Attrs().Name != BRNAME { - veth, isVeth := l.(*netlink.Veth) - Expect(isVeth).To(Equal(true)) - Expect(veth.PeerName).To(Equal(IFNAME)) - } - } + link, err = tops.LinkByName(result.Interface.HostIfname) + Expect(err).NotTo(HaveOccurred()) + veth, isVeth := link.(*netlink.Veth) + Expect(isVeth).To(Equal(true)) + Expect(veth.PeerName).To(Equal(IFNAME)) // Find the veth peer in the container namespace var innerErr error @@ -207,7 +206,7 @@ var _ = Describe("bridge Operations", func() { err = cmdDel(args) Expect(err).NotTo(HaveOccurred()) - // Make sure macvlan link has been deleted + // Make sure veth link has been deleted err = tops.WithNetNS(targetNetNS, false, func(*os.File) error { innerLink, innerErr = tops.LinkByName(IFNAME) return nil @@ -215,5 +214,9 @@ var _ = Describe("bridge Operations", func() { Expect(err).NotTo(HaveOccurred()) Expect(innerErr).To(HaveOccurred()) Expect(innerLink).To(BeNil()) + + link, err = tops.LinkByName(result.Interface.HostIfname) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) }) }) diff --git a/plugins/main/ipvlan/ipvlan.go b/plugins/main/ipvlan/ipvlan.go index 69dae2687..5f136077d 100644 --- a/plugins/main/ipvlan/ipvlan.go +++ b/plugins/main/ipvlan/ipvlan.go @@ -65,22 +65,24 @@ func modeFromString(s string) (netlink.IPVlanMode, error) { } } -func createIpvlan(conf *NetConf, ifName string, netns *os.File) error { +func createIpvlan(conf *NetConf, ifName string, netns *os.File) (*types.InterfaceConfig, error) { + ifaceConfig := &types.InterfaceConfig{} + mode, err := modeFromString(conf.Mode) if err != nil { - return err + return nil, err } m, err := Net().LinkByName(conf.Master) if err != nil { - return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) + return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) } // due to kernel bug we have to create with tmpname or it might // collide with the name on the host and error out - tmpName, err := ip.RandomVethName() + tmpName, err := ip.RandomIfaceName("ipvl") if err != nil { - return err + return nil, err } mv := &netlink.IPVlan{ @@ -94,16 +96,29 @@ func createIpvlan(conf *NetConf, ifName string, netns *os.File) error { } if err := Net().LinkAdd(mv); err != nil { - return fmt.Errorf("failed to create ipvlan: %v", err) + return nil, fmt.Errorf("failed to create ipvlan: %v", err) } - return Net().WithNetNS(netns, false, func(_ *os.File) error { - err := renameLink(tmpName, ifName) + err = Net().WithNetNS(netns, false, func(_ *os.File) error { + ifaceConfig.ContainerIfname, err = ip.RenameLink(tmpName, ifName, "eth%d") if err != nil { return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err) } + + // Re-fetch ipvlan to get all properties/attributes + contIpvlan, err := Net().LinkByName(ifaceConfig.ContainerIfname) + if err != nil { + return fmt.Errorf("failed to refetch ipvlan %q: %v", ifaceConfig.ContainerIfname, err) + } + ifaceConfig.ContainerMac = contIpvlan.Attrs().HardwareAddr.String() + return nil }) + if err != nil { + return nil, err + } + + return ifaceConfig, nil } func cmdAdd(args *skel.CmdArgs) error { @@ -118,7 +133,8 @@ func cmdAdd(args *skel.CmdArgs) error { } defer netns.Close() - if err = createIpvlan(n, args.IfName, netns); err != nil { + ifaceConfig, err := createIpvlan(n, args.IfName, netns) + if err != nil { return err } @@ -139,6 +155,7 @@ func cmdAdd(args *skel.CmdArgs) error { } result.DNS = n.DNS + result.Interface = ifaceConfig return result.Print() } @@ -158,15 +175,6 @@ func cmdDel(args *skel.CmdArgs) error { }) } -func renameLink(curName, newName string) error { - link, err := Net().LinkByName(curName) - if err != nil { - return err - } - - return Net().LinkSetName(link, newName) -} - func main() { skel.PluginMain(cmdAdd, cmdDel) } diff --git a/plugins/main/ipvlan/ipvlan_test.go b/plugins/main/ipvlan/ipvlan_test.go index 8fe36fdd7..fed9e967f 100644 --- a/plugins/main/ipvlan/ipvlan_test.go +++ b/plugins/main/ipvlan/ipvlan_test.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "os" "github.com/appc/cni/pkg/ops" @@ -68,7 +69,7 @@ var _ = Describe("ipvlan Operations", func() { MTU: 1500, } - err = createIpvlan(conf, "foobar0", targetNetNS) + _, err = createIpvlan(conf, "foobar0", targetNetNS) Expect(err).NotTo(HaveOccurred()) // Make sure ipvlan link exists in the target namespace @@ -136,6 +137,7 @@ var _ = Describe("ipvlan Operations", func() { result := types.Result{} err = json.Unmarshal(out, &result) Expect(err).NotTo(HaveOccurred()) + Expect(result.Interface.ContainerIfname).To(Equal(IFNAME)) // Make sure ipvlan link exists in the target namespace var innerErr error @@ -148,6 +150,10 @@ var _ = Describe("ipvlan Operations", func() { Expect(innerErr).NotTo(HaveOccurred()) Expect(innerLink.Attrs().Name).To(Equal(IFNAME)) + hwaddr, err := net.ParseMAC(result.Interface.ContainerMac) + Expect(err).NotTo(HaveOccurred()) + Expect(innerLink.Attrs().HardwareAddr).To(Equal(hwaddr)) + os.Setenv("CNI_COMMAND", "DEL") err = cmdDel(args) Expect(err).NotTo(HaveOccurred()) diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index c2f7a525d..4fb76d1ff 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -69,22 +69,24 @@ func modeFromString(s string) (netlink.MacvlanMode, error) { } } -func createMacvlan(conf *NetConf, ifName string, netns *os.File) error { +func createMacvlan(conf *NetConf, ifName string, netns *os.File) (*types.InterfaceConfig, error) { + ifaceConfig := &types.InterfaceConfig{} + mode, err := modeFromString(conf.Mode) if err != nil { - return err + return nil, err } m, err := Net().LinkByName(conf.Master) if err != nil { - return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) + return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) } // due to kernel bug we have to create with tmpname or it might // collide with the name on the host and error out - tmpName, err := ip.RandomVethName() + tmpName, err := ip.RandomIfaceName("macvl") if err != nil { - return err + return nil, err } mv := &netlink.Macvlan{ @@ -98,16 +100,29 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error { } if err := Net().LinkAdd(mv); err != nil { - return fmt.Errorf("failed to create macvlan: %v", err) + return nil, fmt.Errorf("failed to create macvlan: %v", err) } - return Net().WithNetNS(netns, false, func(_ *os.File) error { - err := renameLink(tmpName, ifName) + err = Net().WithNetNS(netns, false, func(_ *os.File) error { + ifaceConfig.ContainerIfname, err = ip.RenameLink(tmpName, ifName, "eth%d") if err != nil { return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err) } + + // Re-fetch macvlan to get all properties/attributes + contMacvlan, err := Net().LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to refetch macvlan %q: %v", ifName, err) + } + ifaceConfig.ContainerMac = contMacvlan.Attrs().HardwareAddr.String() + return nil }) + if err != nil { + return nil, err + } + + return ifaceConfig, nil } func cmdAdd(args *skel.CmdArgs) error { @@ -122,7 +137,8 @@ func cmdAdd(args *skel.CmdArgs) error { } defer netns.Close() - if err = createMacvlan(n, args.IfName, netns); err != nil { + ifaceConfig, err := createMacvlan(n, args.IfName, netns) + if err != nil { return err } @@ -143,6 +159,7 @@ func cmdAdd(args *skel.CmdArgs) error { } result.DNS = n.DNS + result.Interface = ifaceConfig return result.Print() } @@ -162,15 +179,6 @@ func cmdDel(args *skel.CmdArgs) error { }) } -func renameLink(curName, newName string) error { - link, err := Net().LinkByName(curName) - if err != nil { - return err - } - - return Net().LinkSetName(link, newName) -} - func main() { skel.PluginMain(cmdAdd, cmdDel) } diff --git a/plugins/main/macvlan/macvlan_test.go b/plugins/main/macvlan/macvlan_test.go index ebecba898..f14441382 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "os" "github.com/appc/cni/pkg/ops" @@ -68,7 +69,7 @@ var _ = Describe("macvlan Operations", func() { MTU: 1500, } - err = createMacvlan(conf, "foobar0", targetNetNS) + _, err = createMacvlan(conf, "foobar0", targetNetNS) Expect(err).NotTo(HaveOccurred()) // Make sure macvlan link exists in the target namespace @@ -136,6 +137,7 @@ var _ = Describe("macvlan Operations", func() { result := types.Result{} err = json.Unmarshal(out, &result) Expect(err).NotTo(HaveOccurred()) + Expect(result.Interface.ContainerIfname).To(Equal(IFNAME)) // Make sure macvlan link exists in the target namespace var innerErr error @@ -148,6 +150,10 @@ var _ = Describe("macvlan Operations", func() { Expect(innerErr).NotTo(HaveOccurred()) Expect(innerLink.Attrs().Name).To(Equal(IFNAME)) + hwaddr, err := net.ParseMAC(result.Interface.ContainerMac) + Expect(err).NotTo(HaveOccurred()) + Expect(innerLink.Attrs().HardwareAddr).To(Equal(hwaddr)) + os.Setenv("CNI_COMMAND", "DEL") err = cmdDel(args) Expect(err).NotTo(HaveOccurred()) diff --git a/plugins/main/ptp/ptp.go b/plugins/main/ptp/ptp.go index 3cb8f6438..3859a8fe5 100644 --- a/plugins/main/ptp/ptp.go +++ b/plugins/main/ptp/ptp.go @@ -45,7 +45,7 @@ type NetConf struct { MTU int `json:"mtu"` } -func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string, error) { +func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (*types.InterfaceConfig, error) { // The IPAM result will be something like IP=192.168.3.5/24, GW=192.168.3.1. // What we want is really a point-to-point link but veth does not support IFF_POINTOPONT. // Next best thing would be to let it ARP but set interface to 192.168.3.5/32 and @@ -57,18 +57,24 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string // "192.168.3.1/32 dev $ifName" and "192.168.3.0/24 via 192.168.3.1 dev $ifName". // In other words we force all traffic to ARP via the gateway except for GW itself. - var hostVethName string + ifaceConfig := &types.InterfaceConfig{} + err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error { - hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) + hostVeth, contVeth, err := ip.SetupVeth(ifName, "eth%d", mtu, hostNS) if err != nil { return err } + ifaceConfig.ContainerIfname = contVeth.Attrs().Name + ifaceConfig.ContainerMac = contVeth.Attrs().HardwareAddr.String() + ifaceConfig.HostIfname = hostVeth.Attrs().Name + ifaceConfig.HostMac = hostVeth.Attrs().HardwareAddr.String() if err = ipam.ConfigureIface(ifName, pr); err != nil { return err } - contVeth, err := netlink.LinkByName(ifName) + // Re-fetch container veth to update attributes + contVeth, err = netlink.LinkByName(ifName) if err != nil { return fmt.Errorf("failed to look up %q: %v", ifName, err) } @@ -113,11 +119,12 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string } } - hostVethName = hostVeth.Attrs().Name - return nil }) - return hostVethName, err + if err != nil { + return nil, err + } + return ifaceConfig, nil } func setupHostVeth(vethName string, ipConf *types.IPConfig) error { @@ -168,12 +175,12 @@ func cmdAdd(args *skel.CmdArgs) error { return errors.New("IPAM plugin returned missing IPv4 config") } - hostVethName, err := setupContainerVeth(args.Netns, args.IfName, conf.MTU, result) + ifaceConfig, err := setupContainerVeth(args.Netns, args.IfName, conf.MTU, result) if err != nil { return err } - if err = setupHostVeth(hostVethName, result.IP4); err != nil { + if err = setupHostVeth(ifaceConfig.HostIfname, result.IP4); err != nil { return err } @@ -186,6 +193,7 @@ func cmdAdd(args *skel.CmdArgs) error { } result.DNS = conf.DNS + result.Interface = ifaceConfig return result.Print() }