Skip to content

Commit

Permalink
Pass interface name and MAC back to runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
dcbw committed Mar 3, 2016
1 parent c44bc01 commit 8221cae
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 43 deletions.
20 changes: 13 additions & 7 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand All @@ -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": <name>, (optional)
"hostMac": <MAC address>, (optional)
"containerIfname": <name>, (optional)
"containerMac": <MAC address> (optional)
}
"ip4": {
"ip": <ipv4-and-subnet-in-CIDR>,
"gateway": <ipv4-of-the-gateway>, (optional)
Expand All @@ -100,11 +107,10 @@ Success is indicated by a return code of zero and the following JSON printed to
}
```

`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin.
`dns` field contains a dictionary consisting of common DNS information that this network is aware of.
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.
- `cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin.
- `dns` field contains a dictionary consisting of common DNS information that this network is aware of. 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:
```
Expand Down
7 changes: 7 additions & 0 deletions pkg/ip/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVet
return
}

// Re-fetch container veth to get all properties/attributes
contVeth, err = netlink.LinkByName(contVethName)
if err != nil {
err = fmt.Errorf("failed to lookup %q: %v", contVethName, err)
return
}

if err = netlink.LinkSetUp(contVeth); err != nil {
err = fmt.Errorf("failed to set %q up: %v", contVethName, err)
return
Expand Down
18 changes: 15 additions & 3 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}

Expand All @@ -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
Expand Down
30 changes: 20 additions & 10 deletions plugins/main/bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,35 +122,43 @@ 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) {
if ifName == "" {
ifName = "eth0"
}

ifaceConfig := &types.InterfaceConfig{
ContainerIfname: ifName,
}

err := ns.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, mtu, hostNS)
if err != nil {
return err
}

hostVethName = hostVeth.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 := netlink.LinkByName(hostVethName)
hostVeth, err := netlink.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 = netlink.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 {
Expand Down Expand Up @@ -179,7 +187,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
}

Expand Down Expand Up @@ -227,6 +236,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}

result.DNS = n.DNS
result.Interface = ifaceConfig
return result.Print()
}

Expand Down
37 changes: 30 additions & 7 deletions plugins/main/ipvlan/ipvlan.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,30 @@ 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) {
if ifName == "" {
ifName = "eth0"
}

ifaceConfig := &types.InterfaceConfig{
ContainerIfname: ifName,
}

mode, err := modeFromString(conf.Mode)
if err != nil {
return err
return nil, err
}

m, err := netlink.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()
if err != nil {
return err
return nil, err
}

mv := &netlink.IPVlan{
Expand All @@ -94,16 +102,29 @@ func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
}

if err := netlink.LinkAdd(mv); err != nil {
return fmt.Errorf("failed to create ipvlan: %v", err)
return nil, fmt.Errorf("failed to create ipvlan: %v", err)
}

return ns.WithNetNS(netns, false, func(_ *os.File) error {
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
err := renameLink(tmpName, ifName)
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 := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to refetch ipvlan %q: %v", ifName, err)
}
ifaceConfig.ContainerMac = contIpvlan.Attrs().HardwareAddr.String()

return nil
})
if err != nil {
return nil, err
}

return ifaceConfig, nil
}

func cmdAdd(args *skel.CmdArgs) error {
Expand All @@ -118,7 +139,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
}

Expand All @@ -139,6 +161,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}

result.DNS = n.DNS
result.Interface = ifaceConfig
return result.Print()
}

Expand Down
37 changes: 30 additions & 7 deletions plugins/main/macvlan/macvlan.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,30 @@ 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) {
if ifName == "" {
ifName = "eth0"
}

ifaceConfig := &types.InterfaceConfig{
ContainerIfname: ifName,
}

mode, err := modeFromString(conf.Mode)
if err != nil {
return err
return nil, err
}

m, err := netlink.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()
if err != nil {
return err
return nil, err
}

mv := &netlink.Macvlan{
Expand All @@ -98,16 +106,29 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
}

if err := netlink.LinkAdd(mv); err != nil {
return fmt.Errorf("failed to create macvlan: %v", err)
return nil, fmt.Errorf("failed to create macvlan: %v", err)
}

return ns.WithNetNS(netns, false, func(_ *os.File) error {
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
err := renameLink(tmpName, ifName)
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 := netlink.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 {
Expand All @@ -122,7 +143,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
}

Expand All @@ -143,6 +165,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}

result.DNS = n.DNS
result.Interface = ifaceConfig
return result.Print()
}

Expand Down
Loading

0 comments on commit 8221cae

Please sign in to comment.