Initial IPv6 pass ipv6
authorMichael Cambria <michael.cambria@verizon.com>
Thu, 9 Mar 2017 21:31:34 +0000 (16:31 -0500)
committerMichael Cambria <michael.cambria@verizon.com>
Thu, 9 Mar 2017 21:31:34 +0000 (16:31 -0500)
17 files changed:
16-mynet.conf [new file with mode: 0644]
22-thing2-macvlan0.conf
26-thing2-macvlan0.conf [new file with mode: 0644]
mcc-manual-add.sh
mcc-manual-del.sh
mcc-six-add.sh [new file with mode: 0755]
mcc-six-del.sh [new file with mode: 0755]
pkg/ipam/ipam.go
plugins/ipam/vz-local/backend/allocator/allocator.go
plugins/main/vz-bridge/bridge.go [new file with mode: 0644]
plugins/main/vz-bridge/bridge_suite_test.go [new file with mode: 0644]
plugins/main/vz-bridge/bridge_test.go [new file with mode: 0644]
plugins/main/vz-macvlan/macvlan.go [new file with mode: 0644]
plugins/main/vz-macvlan/macvlan_suite_test.go [new file with mode: 0644]
plugins/main/vz-macvlan/macvlan_test.go [new file with mode: 0644]
thing2-six-add.sh [new file with mode: 0755]
thing2-six-del.sh [new file with mode: 0755]

diff --git a/16-mynet.conf b/16-mynet.conf
new file mode 100644 (file)
index 0000000..8beabd3
--- /dev/null
@@ -0,0 +1,16 @@
+{
+    "cniVersion": "0.2.0",
+    "name": "mynet6",
+    "type": "vz-bridge",
+    "bridge": "cni0",
+  "ipam": {
+        "type": "vz-local",
+        "subnet": "3ffe:ffff:0:01ff::/64",
+        "rangeStart": "3ffe:ffff:0:01ff::0010",
+        "rangeEnd": "3ffe:ffff:0:01ff::0020",
+        "routes": [
+            { "dst": "3ffe:ffff:0:01ff::1/64" }
+        ]
+    }
+}
+
index f52e7ed..8eb199e 100644 (file)
@@ -12,7 +12,7 @@
               { "key" : "app", "value" : "myapp" },
               { "key" : "mccKey1", "value" : "mccVal1" },
               { "key" : "bs", "value" : "bsVal" },
-              { "key" : "StaticIP", "value" : "172.19.0.109" },
+              { "key" : "StaticIP", "value" : "172.19.0.211" },
               { "key" : "Uplink", "value" : "eth1p0" },
               { "key" : "viaIP", "value" : "172.19.0.1" },
               { "key" : "mccKey2", "value" : "mccVal2" },
diff --git a/26-thing2-macvlan0.conf b/26-thing2-macvlan0.conf
new file mode 100644 (file)
index 0000000..fe91f92
--- /dev/null
@@ -0,0 +1,35 @@
+{
+    "cniVersion": "0.2.0",
+    "name": "macvlan6",
+    "type": "vz-macvlan",
+    "master": "eth1p0",
+    "args" : {
+      "org.apache.mesos" : {
+        "network_info" : {
+          "name" : "mynet",
+          "labels" : {
+            "labels" : [
+              { "key" : "app", "value" : "myapp" },
+              { "key" : "mccKey1", "value" : "mccVal1" },
+              { "key" : "bs", "value" : "bsVal" },
+              { "key" : "v4StaticIP", "value" : "172.19.0.209" },
+              { "key" : "v4Uplink", "value" : "eth1p0" },
+              { "key" : "v4viaIP", "value" : "172.19.0.1" },
+              { "key" : "mccKey2", "value" : "mccVal2" },
+              { "key" : "env", "value" : "prod" }
+            ]
+          }
+        }
+      }
+    },
+    "ipam": {
+        "type": "vz-local",
+        "subnet": "3ffe:ffff:0:02ff::/64",
+        "rangeStart": "3ffe:ffff:0:02ff::0010",
+        "rangeEnd": "3ffe:ffff:0:02ff::0020",
+        "routes": [
+            { "dst": "3ffe:ffff:0:02ff::1/64" }
+        ]
+    }
+}
+
index 846ba8e..b74ddfe 100755 (executable)
@@ -1,8 +1,8 @@
 #/bin/bash
 export DEBUG=1
 export NETCONFPATH=/etc/cni/net.d/
-export CNI_PATH=/home/mcambria/go/src/github.com/containernetworking/cni/bin/
-#export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
+#export CNI_PATH=/home/mcambria/go/src/github.com/containernetworking/cni/bin/
+export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
 export NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
 export CNI_COMMAND=ADD
 export CNI_NETNS=/var/run/netns/mcc-cni-test1
index c3af64e..c323c5a 100755 (executable)
@@ -1,8 +1,8 @@
 #/bin/bash
 export DEBUG=1
 export NETCONFPATH=/etc/cni/net.d/
-export CNI_PATH=/home/mcambria/go/src/github.com/containernetworking/cni/bin/
-#export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
+#export CNI_PATH=/home/mcambria/go/src/github.com/containernetworking/cni/bin/
+export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
 export NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
 export CNI_COMMAND=DEL 
 export CNI_NETNS=/var/run/netns/mcc-cni-test1
diff --git a/mcc-six-add.sh b/mcc-six-add.sh
new file mode 100755 (executable)
index 0000000..5122d9d
--- /dev/null
@@ -0,0 +1,17 @@
+#/bin/bash
+export DEBUG=1
+export NETCONFPATH=/etc/cni/net.d/
+export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
+export NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
+export CNI_COMMAND=ADD
+export CNI_NETNS=/var/run/netns/mcc-cni-ipv6
+export CNI_CONTAINERID=mcc-cni-ipv6 
+
+export PATH=$CNI_PATH:$PATH
+export CNI_IFNAME=eth2 
+
+export MCCVAL="Jamal Env Value"
+
+vz-bridge < 16-mynet.conf 
+
+
diff --git a/mcc-six-del.sh b/mcc-six-del.sh
new file mode 100755 (executable)
index 0000000..6057af7
--- /dev/null
@@ -0,0 +1,17 @@
+#/bin/bash
+export DEBUG=1
+export NETCONFPATH=/etc/cni/net.d/
+export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
+export NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
+export CNI_COMMAND=DEL 
+export CNI_NETNS=/var/run/netns/mcc-cni-ipv6
+export CNI_CONTAINERID=mcc-cni-ipv6 
+
+export PATH=$CNI_PATH:$PATH
+export CNI_IFNAME=eth2 
+
+export MCCVAL="Jamal Env Value"
+
+vz-bridge < 16-mynet.conf 
+
+
index 457fe5b..608bbc0 100644 (file)
@@ -18,7 +18,7 @@ import (
        "fmt"
        "net"
        "os"
-        "os/exec"
+        //"os/exec"
 
        "github.com/containernetworking/cni/pkg/invoke"
        "github.com/containernetworking/cni/pkg/ip"
@@ -74,7 +74,7 @@ func ConfigureIface(ifName string, res *current.Result) error {
 
 /*
  * Begin
- */
+ *
         var (
                 cmdOut []byte
                 result string
@@ -94,11 +94,6 @@ func ConfigureIface(ifName string, res *current.Result) error {
         result = string(cmdOut)
         fmt.Println("The result of qdisc show is: ",result)
 
-/*
-        ["ip", "netns", "exec", str(pid), "tc", "filter", "add", "dev", "eth0", "parent", "ffff:", "protocol", "ip",
-         "prio", priority, "u32", "match", "u8", hexgid, "0xf0", "at", "15", "flowid", ":2", "action", "pass”])
-*/
-
         // cmdName = "tc"
         cmdArgs = []string{"filter", "add", "dev", ifName, "parent", "ffff:", "protocol", "ip", "prio", "2", "u32", "match", "u8", "0xd0", "0xf0", "at", "15", "flowid", ":10", "action", "pass" }
         if cmdOut, err = exec.Command(cmdName, cmdArgs...).Output(); err != nil {
@@ -125,12 +120,12 @@ func ConfigureIface(ifName string, res *current.Result) error {
         result = string(cmdOut)
         fmt.Println("The result of show filter is: ", result)
 
-       /*
-        * Example of getting an environment variable supplied to plugin
-        */
+       //
+       // Example of getting an environment variable supplied to plugin
+       //
        mccval := os.Getenv("MCCVAL")
        fmt.Println("mccval is: ", mccval)
-/*
+*
  * End
  */
 
index 64b685d..db3ffda 100644 (file)
@@ -207,8 +207,14 @@ func (a *IPAllocator) Get(id string) (*current.IPConfig, []*types.Route, error)
                                }
                            }
 
+                       var ip_ver = "4"
+                       if requestedIP.To16() != nil {
+                               ip_ver = "6"
+                       }
+
                        ipConfig := &current.IPConfig{
-                               Version: "4",
+                               // Version: "4",
+                               Version: ip_ver,
                                Address: net.IPNet{IP: requestedIP, Mask: a.conf.Subnet.Mask},
                                Gateway: gw,
                        }
@@ -229,9 +235,20 @@ func (a *IPAllocator) Get(id string) (*current.IPConfig, []*types.Route, error)
                if err != nil {
                        return nil, nil, err
                }
+
                if reserved {
+                       var ip_ver string
+                       if cur.To4() != nil {
+                               ip_ver = "4"
+                       } else if cur.To16() != nil {
+                               ip_ver = "6"
+                       } else {
+                               return nil, nil, fmt.Errorf("IP %s not v4 nor v6", cur)
+                       } 
+
                        ipConfig := &current.IPConfig{
-                               Version: "4",
+                               //Version: "4",
+                               Version: ip_ver,
                                Address: net.IPNet{IP: cur, Mask: a.conf.Subnet.Mask},
                                Gateway: gw,
                        }
diff --git a/plugins/main/vz-bridge/bridge.go b/plugins/main/vz-bridge/bridge.go
new file mode 100644 (file)
index 0000000..0388d12
--- /dev/null
@@ -0,0 +1,413 @@
+// Copyright 2014 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       "encoding/json"
+       "errors"
+       "fmt"
+       "net"
+       "runtime"
+       "syscall"
+
+       "github.com/containernetworking/cni/pkg/ip"
+       "github.com/containernetworking/cni/pkg/ipam"
+       "github.com/containernetworking/cni/pkg/ns"
+       "github.com/containernetworking/cni/pkg/skel"
+       "github.com/containernetworking/cni/pkg/types"
+       "github.com/containernetworking/cni/pkg/types/current"
+       "github.com/containernetworking/cni/pkg/utils"
+       "github.com/containernetworking/cni/pkg/version"
+       "github.com/vishvananda/netlink"
+)
+
+const defaultBrName = "cni0"
+
+type NetConf struct {
+       types.NetConf
+       BrName       string `json:"bridge"`
+       IsGW         bool   `json:"isGateway"`
+       IsDefaultGW  bool   `json:"isDefaultGateway"`
+       ForceAddress bool   `json:"forceAddress"`
+       IPMasq       bool   `json:"ipMasq"`
+       MTU          int    `json:"mtu"`
+       HairpinMode  bool   `json:"hairpinMode"`
+}
+
+func init() {
+       // this ensures that main runs only on main thread (thread group leader).
+       // since namespace ops (unshare, setns) are done for a single thread, we
+       // must ensure that the goroutine does not jump from OS thread to thread
+       runtime.LockOSThread()
+}
+
+func loadNetConf(bytes []byte) (*NetConf, string, error) {
+       n := &NetConf{
+               BrName: defaultBrName,
+       }
+       if err := json.Unmarshal(bytes, n); err != nil {
+               return nil, "", fmt.Errorf("failed to load netconf: %v", err)
+       }
+       return n, n.CNIVersion, nil
+}
+
+func ensureBridgeAddr(br *netlink.Bridge, ipn *net.IPNet, forceAddress bool) error {
+       addrs, err := netlink.AddrList(br, syscall.AF_INET)
+       if err != nil && err != syscall.ENOENT {
+               return fmt.Errorf("could not get list of IP addresses: %v", err)
+       }
+
+       // if there're no addresses on the bridge, it's ok -- we'll add one
+       if len(addrs) > 0 {
+               ipnStr := ipn.String()
+               for _, a := range addrs {
+                       // string comp is actually easiest for doing IPNet comps
+                       if a.IPNet.String() == ipnStr {
+                               return nil
+                       }
+
+                       // If forceAddress is set to true then reconfigure IP address otherwise throw error
+                       if forceAddress {
+                               if err = deleteBridgeAddr(br, a.IPNet); err != nil {
+                                       return err
+                               }
+                       } else {
+                               return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipn.String())
+                       }
+               }
+       }
+
+       addr := &netlink.Addr{IPNet: ipn, Label: ""}
+       if err := netlink.AddrAdd(br, addr); err != nil {
+               return fmt.Errorf("could not add IP address to %q: %v", br.Name, err)
+       }
+       return nil
+}
+
+func deleteBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error {
+       addr := &netlink.Addr{IPNet: ipn, Label: ""}
+
+       if err := netlink.LinkSetDown(br); err != nil {
+               return fmt.Errorf("could not set down bridge %q: %v", br.Name, err)
+       }
+
+       if err := netlink.AddrDel(br, addr); err != nil {
+               return fmt.Errorf("could not remove IP address from %q: %v", br.Name, err)
+       }
+
+       if err := netlink.LinkSetUp(br); err != nil {
+               return fmt.Errorf("could not set up bridge %q: %v", br.Name, err)
+       }
+
+       return nil
+}
+
+func bridgeByName(name string) (*netlink.Bridge, error) {
+       l, err := netlink.LinkByName(name)
+       if err != nil {
+               return nil, fmt.Errorf("could not lookup %q: %v", name, err)
+       }
+       br, ok := l.(*netlink.Bridge)
+       if !ok {
+               return nil, fmt.Errorf("%q already exists but is not a bridge", name)
+       }
+       return br, nil
+}
+
+func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
+       br := &netlink.Bridge{
+               LinkAttrs: netlink.LinkAttrs{
+                       Name: brName,
+                       MTU:  mtu,
+                       // Let kernel use default txqueuelen; leaving it unset
+                       // means 0, and a zero-length TX queue messes up FIFO
+                       // traffic shapers which use TX queue length as the
+                       // default packet limit
+                       TxQLen: -1,
+               },
+       }
+
+       err := netlink.LinkAdd(br)
+       if err != nil && err != syscall.EEXIST {
+               return nil, fmt.Errorf("could not add %q: %v", brName, err)
+       }
+
+       // Re-fetch link to read all attributes and if it already existed,
+       // ensure it's really a bridge with similar configuration
+       br, err = bridgeByName(brName)
+       if err != nil {
+               return nil, err
+       }
+
+       if err := netlink.LinkSetUp(br); err != nil {
+               return nil, err
+       }
+
+       return br, nil
+}
+
+func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) (*current.Interface, *current.Interface, error) {
+       contIface := &current.Interface{}
+       hostIface := &current.Interface{}
+
+       err := netns.Do(func(hostNS ns.NetNS) error {
+               // create the veth pair in the container and move host end into host netns
+               hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS)
+               if err != nil {
+                       return err
+               }
+               contIface.Name = containerVeth.Attrs().Name
+               contIface.Mac = containerVeth.Attrs().HardwareAddr.String()
+               contIface.Sandbox = netns.Path()
+               hostIface.Name = hostVeth.Attrs().Name
+               return nil
+       })
+       if err != nil {
+               return nil, nil, err
+       }
+
+       // need to lookup hostVeth again as its index has changed during ns move
+       hostVeth, err := netlink.LinkByName(hostIface.Name)
+       if err != nil {
+               return nil, nil, fmt.Errorf("failed to lookup %q: %v", hostIface.Name, err)
+       }
+       hostIface.Mac = hostVeth.Attrs().HardwareAddr.String()
+
+       // connect host veth end to the bridge
+       if err := netlink.LinkSetMaster(hostVeth, br); err != nil {
+               return nil, nil, fmt.Errorf("failed to connect %q to bridge %v: %v", hostVeth.Attrs().Name, br.Attrs().Name, err)
+       }
+
+       // set hairpin mode
+       if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
+               return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
+       }
+
+       return hostIface, contIface, nil
+}
+
+func calcGatewayIP(ipn *net.IPNet) net.IP {
+       nid := ipn.IP.Mask(ipn.Mask)
+       return ip.NextIP(nid)
+}
+
+func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
+       // create bridge if necessary
+       br, err := ensureBridge(n.BrName, n.MTU)
+       if err != nil {
+               return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
+       }
+
+       return br, &current.Interface{
+               Name: br.Attrs().Name,
+               Mac:  br.Attrs().HardwareAddr.String(),
+       }, nil
+}
+
+func cmdAdd(args *skel.CmdArgs) error {
+       n, cniVersion, err := loadNetConf(args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       if n.IsDefaultGW {
+               n.IsGW = true
+       }
+
+       br, brInterface, err := setupBridge(n)
+       if err != nil {
+               return err
+       }
+
+       netns, err := ns.GetNS(args.Netns)
+       if err != nil {
+               return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
+       }
+       defer netns.Close()
+
+       hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)
+       if err != nil {
+               return err
+       }
+
+       // run the IPAM plugin and get back the config to apply
+       r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       // Convert whatever the IPAM result was into the current Result type
+       result, err := current.NewResultFromResult(r)
+       if err != nil {
+               return err
+       }
+
+       if len(result.IPs) == 0 {
+               return errors.New("IPAM plugin returned missing IP config")
+       }
+
+       result.Interfaces = []*current.Interface{brInterface, hostInterface, containerInterface}
+
+       for _, ipc := range result.IPs {
+               // All IPs currently refer to the container interface
+               ipc.Interface = 2
+               if ipc.Gateway == nil && n.IsGW {
+                       ipc.Gateway = calcGatewayIP(&ipc.Address)
+               }
+       }
+
+       if err := netns.Do(func(_ ns.NetNS) error {
+               // set the default gateway if requested
+               if n.IsDefaultGW {
+                       for _, ipc := range result.IPs {
+                               defaultNet := &net.IPNet{}
+                               switch {
+                               case ipc.Address.IP.To4() != nil:
+                                       defaultNet.IP = net.IPv4zero
+                                       defaultNet.Mask = net.IPMask(net.IPv4zero)
+                               case len(ipc.Address.IP) == net.IPv6len && ipc.Address.IP.To4() == nil:
+                                       defaultNet.IP = net.IPv6zero
+                                       defaultNet.Mask = net.IPMask(net.IPv6zero)
+                               default:
+                                       return fmt.Errorf("Unknown IP object: %v", ipc)
+                               }
+
+                               for _, route := range result.Routes {
+                                       if defaultNet.String() == route.Dst.String() {
+                                               if route.GW != nil && !route.GW.Equal(ipc.Gateway) {
+                                                       return fmt.Errorf(
+                                                               "isDefaultGateway ineffective because IPAM sets default route via %q",
+                                                               route.GW,
+                                                       )
+                                               }
+                                       }
+                               }
+
+                               result.Routes = append(
+                                       result.Routes,
+                                       &types.Route{Dst: *defaultNet, GW: ipc.Gateway},
+                               )
+                       }
+               }
+
+               if err := ipam.ConfigureIface(args.IfName, result); err != nil {
+                       return err
+               }
+
+               //if err := ip.SetHWAddrByIP(args.IfName, result.IPs[0].Address.IP, nil /* TODO IPv6 */); err != nil {
+               //      return err
+               //}
+
+               // Refetch the veth since its MAC address may changed
+               link, err := netlink.LinkByName(args.IfName)
+               if err != nil {
+                       return fmt.Errorf("could not lookup %q: %v", args.IfName, err)
+               }
+               containerInterface.Mac = link.Attrs().HardwareAddr.String()
+
+               return nil
+       }); err != nil {
+               return err
+       }
+
+       if n.IsGW {
+               var firstV4Addr net.IP
+               for _, ipc := range result.IPs {
+                       gwn := &net.IPNet{
+                               IP:   ipc.Gateway,
+                               Mask: ipc.Address.Mask,
+                       }
+                       if ipc.Gateway.To4() != nil && firstV4Addr == nil {
+                               firstV4Addr = ipc.Gateway
+                       }
+
+                       if err = ensureBridgeAddr(br, gwn, n.ForceAddress); err != nil {
+                               return err
+                       }
+               }
+
+               if firstV4Addr != nil {
+                       if err := ip.SetHWAddrByIP(n.BrName, firstV4Addr, nil /* TODO IPv6 */); err != nil {
+                               return err
+                       }
+               }
+
+               if err := ip.EnableIP4Forward(); err != nil {
+                       return fmt.Errorf("failed to enable forwarding: %v", err)
+               }
+       }
+
+       if n.IPMasq {
+               chain := utils.FormatChainName(n.Name, args.ContainerID)
+               comment := utils.FormatComment(n.Name, args.ContainerID)
+               for _, ipc := range result.IPs {
+                       if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // Refetch the bridge since its MAC address may change when the first
+       // veth is added or after its IP address is set
+       br, err = bridgeByName(n.BrName)
+       if err != nil {
+               return err
+       }
+       brInterface.Mac = br.Attrs().HardwareAddr.String()
+
+       result.DNS = n.DNS
+
+       return types.PrintResult(result, cniVersion)
+}
+
+func cmdDel(args *skel.CmdArgs) error {
+       n, _, err := loadNetConf(args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
+               return err
+       }
+
+       if args.Netns == "" {
+               return nil
+       }
+
+       var ipn *net.IPNet
+       err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
+               var err error
+               // ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
+               err = ip.DelLinkByName(args.IfName)
+               return err
+       })
+       if err != nil {
+               return err
+       }
+
+       if n.IPMasq {
+               chain := utils.FormatChainName(n.Name, args.ContainerID)
+               comment := utils.FormatComment(n.Name, args.ContainerID)
+               if err = ip.TeardownIPMasq(ipn, chain, comment); err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+func main() {
+       skel.PluginMain(cmdAdd, cmdDel, version.All)
+}
diff --git a/plugins/main/vz-bridge/bridge_suite_test.go b/plugins/main/vz-bridge/bridge_suite_test.go
new file mode 100644 (file)
index 0000000..9aa214e
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+
+       "testing"
+)
+
+func TestBridge(t *testing.T) {
+       RegisterFailHandler(Fail)
+       RunSpecs(t, "bridge Suite")
+}
diff --git a/plugins/main/vz-bridge/bridge_test.go b/plugins/main/vz-bridge/bridge_test.go
new file mode 100644 (file)
index 0000000..5a8fd5a
--- /dev/null
@@ -0,0 +1,478 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       "fmt"
+       "net"
+       "strings"
+       "syscall"
+
+       "github.com/containernetworking/cni/pkg/ns"
+       "github.com/containernetworking/cni/pkg/skel"
+       "github.com/containernetworking/cni/pkg/testutils"
+       "github.com/containernetworking/cni/pkg/types"
+       "github.com/containernetworking/cni/pkg/types/020"
+       "github.com/containernetworking/cni/pkg/types/current"
+
+       "github.com/containernetworking/cni/pkg/utils/hwaddr"
+
+       "github.com/vishvananda/netlink"
+
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+)
+
+var _ = Describe("bridge Operations", func() {
+       var originalNS ns.NetNS
+
+       BeforeEach(func() {
+               // Create a new NetNS so we don't modify the host
+               var err error
+               originalNS, err = ns.NewNS()
+               Expect(err).NotTo(HaveOccurred())
+       })
+
+       AfterEach(func() {
+               Expect(originalNS.Close()).To(Succeed())
+       })
+
+       It("creates a bridge", func() {
+               const IFNAME = "bridge0"
+
+               conf := &NetConf{
+                       NetConf: types.NetConf{
+                               CNIVersion: "0.3.0",
+                               Name:       "testConfig",
+                               Type:       "bridge",
+                       },
+                       BrName: IFNAME,
+                       IsGW:   false,
+                       IPMasq: false,
+                       MTU:    5000,
+               }
+
+               err := originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       bridge, _, err := setupBridge(conf)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(bridge.Attrs().Name).To(Equal(IFNAME))
+
+                       // Double check that the link was added
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(IFNAME))
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+       })
+
+       It("handles an existing bridge", func() {
+               const IFNAME = "bridge0"
+
+               err := originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       err := netlink.LinkAdd(&netlink.Bridge{
+                               LinkAttrs: netlink.LinkAttrs{
+                                       Name: IFNAME,
+                               },
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(IFNAME))
+                       ifindex := link.Attrs().Index
+
+                       conf := &NetConf{
+                               NetConf: types.NetConf{
+                                       CNIVersion: "0.3.0",
+                                       Name:       "testConfig",
+                                       Type:       "bridge",
+                               },
+                               BrName: IFNAME,
+                               IsGW:   false,
+                               IPMasq: false,
+                       }
+
+                       bridge, _, err := setupBridge(conf)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(bridge.Attrs().Name).To(Equal(IFNAME))
+                       Expect(bridge.Attrs().Index).To(Equal(ifindex))
+
+                       // Double check that the link has the same ifindex
+                       link, err = netlink.LinkByName(IFNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(IFNAME))
+                       Expect(link.Attrs().Index).To(Equal(ifindex))
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+       })
+
+       It("configures and deconfigures a bridge and veth with default route with ADD/DEL", func() {
+               const BRNAME = "cni0"
+               const IFNAME = "eth0"
+
+               gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24")
+               Expect(err).NotTo(HaveOccurred())
+
+               conf := fmt.Sprintf(`{
+    "cniVersion": "0.3.0",
+    "name": "mynet",
+    "type": "bridge",
+    "bridge": "%s",
+    "isDefaultGateway": true,
+    "ipMasq": false,
+    "ipam": {
+        "type": "host-local",
+        "subnet": "%s"
+    }
+}`, BRNAME, subnet.String())
+
+               targetNs, err := ns.NewNS()
+               Expect(err).NotTo(HaveOccurred())
+               defer targetNs.Close()
+
+               args := &skel.CmdArgs{
+                       ContainerID: "dummy",
+                       Netns:       targetNs.Path(),
+                       IfName:      IFNAME,
+                       StdinData:   []byte(conf),
+               }
+
+               var result *current.Result
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       r, raw, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
+                               return cmdAdd(args)
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
+
+                       result, err = current.GetResult(r)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       Expect(len(result.Interfaces)).To(Equal(3))
+                       Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
+                       Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
+
+                       // Make sure bridge link exists
+                       link, err := netlink.LinkByName(result.Interfaces[0].Name)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(BRNAME))
+                       Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
+                       Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
+                       hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
+                       Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
+
+                       // Ensure bridge has gateway address
+                       addrs, err := netlink.AddrList(link, syscall.AF_INET)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(addrs)).To(BeNumerically(">", 0))
+                       found := false
+                       subnetPrefix, subnetBits := subnet.Mask.Size()
+                       for _, a := range addrs {
+                               aPrefix, aBits := a.IPNet.Mask.Size()
+                               if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
+                                       found = true
+                                       break
+                               }
+                       }
+                       Expect(found).To(Equal(true))
+
+                       // Check for the veth link in the main namespace
+                       links, err := netlink.LinkList()
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
+
+                       link, err = netlink.LinkByName(result.Interfaces[1].Name)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Find the veth peer in the container namespace and the default route
+               err = targetNs.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(IFNAME))
+                       Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
+
+                       addrs, err := netlink.AddrList(link, syscall.AF_INET)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(addrs)).To(Equal(1))
+
+                       hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
+                       Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
+
+                       // Ensure the default route
+                       routes, err := netlink.RouteList(link, 0)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       var defaultRouteFound bool
+                       for _, route := range routes {
+                               defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
+                               if defaultRouteFound {
+                                       break
+                               }
+                       }
+                       Expect(defaultRouteFound).To(Equal(true))
+
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
+                               return cmdDel(args)
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Make sure the host veth has been deleted
+               err = targetNs.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).To(HaveOccurred())
+                       Expect(link).To(BeNil())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Make sure the container veth has been deleted
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(result.Interfaces[1].Name)
+                       Expect(err).To(HaveOccurred())
+                       Expect(link).To(BeNil())
+                       return nil
+               })
+       })
+
+       It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() {
+               const BRNAME = "cni0"
+               const IFNAME = "eth0"
+
+               gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24")
+               Expect(err).NotTo(HaveOccurred())
+
+               conf := fmt.Sprintf(`{
+    "cniVersion": "0.1.0",
+    "name": "mynet",
+    "type": "bridge",
+    "bridge": "%s",
+    "isDefaultGateway": true,
+    "ipMasq": false,
+    "ipam": {
+        "type": "host-local",
+        "subnet": "%s"
+    }
+}`, BRNAME, subnet.String())
+
+               targetNs, err := ns.NewNS()
+               Expect(err).NotTo(HaveOccurred())
+               defer targetNs.Close()
+
+               args := &skel.CmdArgs{
+                       ContainerID: "dummy",
+                       Netns:       targetNs.Path(),
+                       IfName:      IFNAME,
+                       StdinData:   []byte(conf),
+               }
+
+               var result *types020.Result
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       r, raw, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
+                               return cmdAdd(args)
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(strings.Index(string(raw), "\"ip4\":")).Should(BeNumerically(">", 0))
+
+                       // We expect a version 0.1.0 result
+                       result, err = types020.GetResult(r)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       // Make sure bridge link exists
+                       link, err := netlink.LinkByName(BRNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(BRNAME))
+                       Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
+                       hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
+                       Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
+
+                       // Ensure bridge has gateway address
+                       addrs, err := netlink.AddrList(link, syscall.AF_INET)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(addrs)).To(BeNumerically(">", 0))
+                       found := false
+                       subnetPrefix, subnetBits := subnet.Mask.Size()
+                       for _, a := range addrs {
+                               aPrefix, aBits := a.IPNet.Mask.Size()
+                               if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
+                                       found = true
+                                       break
+                               }
+                       }
+                       Expect(found).To(Equal(true))
+
+                       // Check for the veth link in the main namespace; can't
+                       // check the for the specific link since version 0.1.0
+                       // doesn't report interfaces
+                       links, err := netlink.LinkList()
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Find the veth peer in the container namespace and the default route
+               err = targetNs.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(IFNAME))
+                       Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
+
+                       addrs, err := netlink.AddrList(link, syscall.AF_INET)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(addrs)).To(Equal(1))
+
+                       hwAddr := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
+                       Expect(hwAddr).To(HavePrefix(hwaddr.PrivateMACPrefixString))
+
+                       // Ensure the default route
+                       routes, err := netlink.RouteList(link, 0)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       var defaultRouteFound bool
+                       for _, route := range routes {
+                               defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
+                               if defaultRouteFound {
+                                       break
+                               }
+                       }
+                       Expect(defaultRouteFound).To(Equal(true))
+
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
+                               return cmdDel(args)
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Make sure the container veth has been deleted; cannot check
+               // host veth as version 0.1.0 can't report its name
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).To(HaveOccurred())
+                       Expect(link).To(BeNil())
+                       return nil
+               })
+       })
+
+       It("ensure bridge address", func() {
+               const IFNAME = "bridge0"
+               const EXPECTED_IP = "10.0.0.0/8"
+               const CHANGED_EXPECTED_IP = "10.1.2.3/16"
+
+               conf := &NetConf{
+                       NetConf: types.NetConf{
+                               CNIVersion: "0.3.0",
+                               Name:       "testConfig",
+                               Type:       "bridge",
+                       },
+                       BrName: IFNAME,
+                       IsGW:   true,
+                       IPMasq: false,
+                       MTU:    5000,
+               }
+
+               gwnFirst := &net.IPNet{
+                       IP:   net.IPv4(10, 0, 0, 0),
+                       Mask: net.CIDRMask(8, 32),
+               }
+
+               gwnSecond := &net.IPNet{
+                       IP:   net.IPv4(10, 1, 2, 3),
+                       Mask: net.CIDRMask(16, 32),
+               }
+
+               err := originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       bridge, _, err := setupBridge(conf)
+                       Expect(err).NotTo(HaveOccurred())
+                       // Check if ForceAddress has default value
+                       Expect(conf.ForceAddress).To(Equal(false))
+
+                       err = ensureBridgeAddr(bridge, gwnFirst, conf.ForceAddress)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       //Check if IP address is set correctly
+                       addrs, err := netlink.AddrList(bridge, syscall.AF_INET)
+                       Expect(len(addrs)).To(Equal(1))
+                       addr := addrs[0].IPNet.String()
+                       Expect(addr).To(Equal(EXPECTED_IP))
+
+                       //The bridge IP address has been changed. Error expected when ForceAddress is set to false.
+                       err = ensureBridgeAddr(bridge, gwnSecond, false)
+                       Expect(err).To(HaveOccurred())
+
+                       //The IP address should stay the same.
+                       addrs, err = netlink.AddrList(bridge, syscall.AF_INET)
+                       Expect(len(addrs)).To(Equal(1))
+                       addr = addrs[0].IPNet.String()
+                       Expect(addr).To(Equal(EXPECTED_IP))
+
+                       //Reconfigure IP when ForceAddress is set to true and IP address has been changed.
+                       err = ensureBridgeAddr(bridge, gwnSecond, true)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       //Retrieve the IP address after reconfiguration
+                       addrs, err = netlink.AddrList(bridge, syscall.AF_INET)
+                       Expect(len(addrs)).To(Equal(1))
+                       addr = addrs[0].IPNet.String()
+                       Expect(addr).To(Equal(CHANGED_EXPECTED_IP))
+
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+       })
+})
diff --git a/plugins/main/vz-macvlan/macvlan.go b/plugins/main/vz-macvlan/macvlan.go
new file mode 100644 (file)
index 0000000..0409c04
--- /dev/null
@@ -0,0 +1,270 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       "encoding/json"
+       "errors"
+       "fmt"
+       "net"
+       "runtime"
+
+       "github.com/containernetworking/cni/pkg/ip"
+       "github.com/containernetworking/cni/pkg/ipam"
+       "github.com/containernetworking/cni/pkg/ns"
+       "github.com/containernetworking/cni/pkg/skel"
+       "github.com/containernetworking/cni/pkg/types"
+       "github.com/containernetworking/cni/pkg/types/current"
+       "github.com/containernetworking/cni/pkg/utils/sysctl"
+       "github.com/containernetworking/cni/pkg/version"
+       "github.com/vishvananda/netlink"
+)
+
+const (
+       IPv4InterfaceArpProxySysctlTemplate = "net.ipv4.conf.%s.proxy_arp"
+)
+
+type NetConf struct {
+       types.NetConf
+       Master string `json:"master"`
+       Mode   string `json:"mode"`
+       MTU    int    `json:"mtu"`
+}
+
+func init() {
+       // this ensures that main runs only on main thread (thread group leader).
+       // since namespace ops (unshare, setns) are done for a single thread, we
+       // must ensure that the goroutine does not jump from OS thread to thread
+       runtime.LockOSThread()
+}
+
+func loadConf(bytes []byte) (*NetConf, string, error) {
+       n := &NetConf{}
+       if err := json.Unmarshal(bytes, n); err != nil {
+               return nil, "", fmt.Errorf("failed to load netconf: %v", err)
+       }
+       if n.Master == "" {
+               return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
+       }
+       return n, n.CNIVersion, nil
+}
+
+func modeFromString(s string) (netlink.MacvlanMode, error) {
+       switch s {
+       case "", "bridge":
+               return netlink.MACVLAN_MODE_BRIDGE, nil
+       case "private":
+               return netlink.MACVLAN_MODE_PRIVATE, nil
+       case "vepa":
+               return netlink.MACVLAN_MODE_VEPA, nil
+       case "passthru":
+               return netlink.MACVLAN_MODE_PASSTHRU, nil
+       default:
+               return 0, fmt.Errorf("unknown macvlan mode: %q", s)
+       }
+}
+
+func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
+       macvlan := &current.Interface{}
+
+       mode, err := modeFromString(conf.Mode)
+       if err != nil {
+               return nil, err
+       }
+
+println("createMacvlan called")
+       m, err := netlink.LinkByName(conf.Master)
+       if err != nil {
+               return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
+       }
+
+println("master found")
+       // 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 nil, err
+       }
+
+       mv := &netlink.Macvlan{
+               LinkAttrs: netlink.LinkAttrs{
+                       MTU:         conf.MTU,
+                       Name:        tmpName,
+                       ParentIndex: m.Attrs().Index,
+                       Namespace:   netlink.NsFd(int(netns.Fd())),
+               },
+               Mode: mode,
+       }
+
+       if err := netlink.LinkAdd(mv); err != nil {
+               return nil, fmt.Errorf("failed to create macvlan: %v", err)
+       }
+println("add worked")
+       err = netns.Do(func(_ ns.NetNS) error {
+               // TODO: duplicate following lines for ipv6 support, when it will be added in other places
+               ipv4SysctlValueName := fmt.Sprintf(IPv4InterfaceArpProxySysctlTemplate, tmpName)
+               if _, err := sysctl.Sysctl(ipv4SysctlValueName, "1"); err != nil {
+                       // remove the newly added link and ignore errors, because we already are in a failed state
+                       _ = netlink.LinkDel(mv)
+                       return fmt.Errorf("failed to set proxy_arp on newly added interface %q: %v", tmpName, err)
+               }
+println("ipv4 stuff worked")
+               err := ip.RenameLink(tmpName, ifName)
+               if err != nil {
+                       _ = netlink.LinkDel(mv)
+                       return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err)
+               }
+               macvlan.Name = ifName
+println("macvlan name is ", ifName)
+
+               // 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)
+               }
+               macvlan.Mac = contMacvlan.Attrs().HardwareAddr.String()
+               macvlan.Sandbox = netns.Path()
+println("return after re-fetch")
+               return nil
+       })
+       if err != nil {
+               return nil, err
+       }
+println("end of function return")
+
+       return macvlan, nil
+}
+
+func cmdAdd(args *skel.CmdArgs) error {
+       n, cniVersion, err := loadConf(args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       netns, err := ns.GetNS(args.Netns)
+       if err != nil {
+               return fmt.Errorf("failed to open netns %q: %v", netns, err)
+       }
+       defer netns.Close()
+
+       macvlanInterface, err := createMacvlan(n, args.IfName, netns)
+       if err != nil {
+               return err
+       }
+
+       // run the IPAM plugin and get back the config to apply
+       r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
+       if err != nil {
+               return err
+       }
+       // Convert whatever the IPAM result was into the current Result type
+       result, err := current.NewResultFromResult(r)
+       if err != nil {
+               return err
+       }
+
+println("IPAM returned ok")
+       if len(result.IPs) == 0 {
+               return errors.New("IPAM plugin returned missing IP config")
+       }
+       result.Interfaces = []*current.Interface{macvlanInterface}
+
+       var firstV4Addr net.IP
+       var firstV6Addr net.IP
+       for _, ipc := range result.IPs {
+               // All addresses apply to the container macvlan interface
+               ipc.Interface = 0
+
+               if ipc.Address.IP.To4() != nil && firstV4Addr == nil {
+                       firstV4Addr = ipc.Address.IP
+               }
+
+                if ipc.Address.IP.To16() != nil && firstV6Addr == nil {
+                        firstV6Addr = ipc.Address.IP
+                }
+       }
+
+       println("looking at first V4 addr")
+       if firstV4Addr != nil {
+               err = netns.Do(func(_ ns.NetNS) error {
+                       if err := ip.SetHWAddrByIP(args.IfName, firstV4Addr, nil /* TODO IPv6 */); err != nil {
+                               return err
+                       }
+
+                       println("return ipv4 config interface")
+                       return ipam.ConfigureIface(args.IfName, result)
+               })
+               if err != nil {
+                       return err
+               }
+       }
+       println("done with first V4 addr")
+
+       println("looking at first V6 addr")
+        if firstV6Addr != nil {
+                err = netns.Do(func(_ ns.NetNS) error {
+
+                       println("return ipv6 config interface")
+                        return ipam.ConfigureIface(args.IfName, result)
+                })
+                if err != nil {
+                        return err
+                }
+        }
+       println("done with first V6 addr")
+
+       // Re-fetch macvlan interface as its MAC address may have changed
+       err = netns.Do(func(_ ns.NetNS) error {
+               link, err := netlink.LinkByName(args.IfName)
+               if err != nil {
+                       return fmt.Errorf("failed to re-fetch macvlan interface: %v", err)
+               }
+               macvlanInterface.Mac = link.Attrs().HardwareAddr.String()
+               return nil
+       })
+       if err != nil {
+               return err
+       }
+
+       result.DNS = n.DNS
+
+println("end of function return")
+
+       return types.PrintResult(result, cniVersion)
+}
+
+func cmdDel(args *skel.CmdArgs) error {
+       n, _, err := loadConf(args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
+       if err != nil {
+               return err
+       }
+
+       if args.Netns == "" {
+               return nil
+       }
+
+       return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
+               return ip.DelLinkByName(args.IfName)
+       })
+}
+
+func main() {
+       skel.PluginMain(cmdAdd, cmdDel, version.All)
+}
diff --git a/plugins/main/vz-macvlan/macvlan_suite_test.go b/plugins/main/vz-macvlan/macvlan_suite_test.go
new file mode 100644 (file)
index 0000000..844e567
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+
+       "testing"
+)
+
+func TestMacvlan(t *testing.T) {
+       RegisterFailHandler(Fail)
+       RunSpecs(t, "macvlan Suite")
+}
diff --git a/plugins/main/vz-macvlan/macvlan_test.go b/plugins/main/vz-macvlan/macvlan_test.go
new file mode 100644 (file)
index 0000000..dfb4e8c
--- /dev/null
@@ -0,0 +1,192 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       "fmt"
+       "net"
+       "syscall"
+
+       "github.com/containernetworking/cni/pkg/ns"
+       "github.com/containernetworking/cni/pkg/skel"
+       "github.com/containernetworking/cni/pkg/testutils"
+       "github.com/containernetworking/cni/pkg/types"
+       "github.com/containernetworking/cni/pkg/types/current"
+       "github.com/containernetworking/cni/pkg/utils/hwaddr"
+
+       "github.com/vishvananda/netlink"
+
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+)
+
+const MASTER_NAME = "eth0"
+
+var _ = Describe("macvlan Operations", func() {
+       var originalNS ns.NetNS
+
+       BeforeEach(func() {
+               // Create a new NetNS so we don't modify the host
+               var err error
+               originalNS, err = ns.NewNS()
+               Expect(err).NotTo(HaveOccurred())
+
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       // Add master
+                       err = netlink.LinkAdd(&netlink.Dummy{
+                               LinkAttrs: netlink.LinkAttrs{
+                                       Name: MASTER_NAME,
+                               },
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       _, err = netlink.LinkByName(MASTER_NAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+       })
+
+       AfterEach(func() {
+               Expect(originalNS.Close()).To(Succeed())
+       })
+
+       It("creates an macvlan link in a non-default namespace", func() {
+               conf := &NetConf{
+                       NetConf: types.NetConf{
+                               CNIVersion: "0.3.0",
+                               Name:       "testConfig",
+                               Type:       "macvlan",
+                       },
+                       Master: MASTER_NAME,
+                       Mode:   "bridge",
+                       MTU:    1500,
+               }
+
+               targetNs, err := ns.NewNS()
+               Expect(err).NotTo(HaveOccurred())
+               defer targetNs.Close()
+
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       _, err = createMacvlan(conf, "foobar0", targetNs)
+                       Expect(err).NotTo(HaveOccurred())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Make sure macvlan link exists in the target namespace
+               err = targetNs.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName("foobar0")
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal("foobar0"))
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+       })
+
+       It("configures and deconfigures a macvlan link with ADD/DEL", func() {
+               const IFNAME = "macvl0"
+
+               conf := fmt.Sprintf(`{
+    "cniVersion": "0.3.0",
+    "name": "mynet",
+    "type": "macvlan",
+    "master": "%s",
+    "ipam": {
+        "type": "host-local",
+        "subnet": "10.1.2.0/24"
+    }
+}`, MASTER_NAME)
+
+               targetNs, err := ns.NewNS()
+               Expect(err).NotTo(HaveOccurred())
+               defer targetNs.Close()
+
+               args := &skel.CmdArgs{
+                       ContainerID: "dummy",
+                       Netns:       targetNs.Path(),
+                       IfName:      IFNAME,
+                       StdinData:   []byte(conf),
+               }
+
+               var result *current.Result
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       r, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error {
+                               return cmdAdd(args)
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+
+                       result, err = current.GetResult(r)
+                       Expect(err).NotTo(HaveOccurred())
+
+                       Expect(len(result.Interfaces)).To(Equal(1))
+                       Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
+                       Expect(len(result.IPs)).To(Equal(1))
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Make sure macvlan link exists in the target namespace
+               err = targetNs.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().Name).To(Equal(IFNAME))
+
+                       hwaddrString := fmt.Sprintf("%s", link.Attrs().HardwareAddr)
+                       Expect(hwaddrString).To(HavePrefix(hwaddr.PrivateMACPrefixString))
+
+                       hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
+
+                       addrs, err := netlink.AddrList(link, syscall.AF_INET)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(len(addrs)).To(Equal(1))
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               err = originalNS.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
+                               return cmdDel(args)
+                       })
+                       Expect(err).NotTo(HaveOccurred())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               // Make sure macvlan link has been deleted
+               err = targetNs.Do(func(ns.NetNS) error {
+                       defer GinkgoRecover()
+
+                       link, err := netlink.LinkByName(IFNAME)
+                       Expect(err).To(HaveOccurred())
+                       Expect(link).To(BeNil())
+                       return nil
+               })
+               Expect(err).NotTo(HaveOccurred())
+       })
+})
diff --git a/thing2-six-add.sh b/thing2-six-add.sh
new file mode 100755 (executable)
index 0000000..3aad1f0
--- /dev/null
@@ -0,0 +1,17 @@
+#/bin/bash
+export DEBUG=1
+export NETCONFPATH=/etc/cni/net.d/
+export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
+export NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
+export CNI_COMMAND=ADD
+export CNI_NETNS=/var/run/netns/mcc-cni-ipv6
+export CNI_CONTAINERID=mcc-cni-ipv6 
+
+export PATH=$CNI_PATH:$PATH
+export CNI_IFNAME=eth1 
+
+export MCCVAL="Jamal Env Value"
+
+vz-macvlan < 26-thing2-macvlan0.conf
+
+
diff --git a/thing2-six-del.sh b/thing2-six-del.sh
new file mode 100755 (executable)
index 0000000..2b8ae56
--- /dev/null
@@ -0,0 +1,17 @@
+#/bin/bash
+export DEBUG=1
+export NETCONFPATH=/etc/cni/net.d/
+export CNI_PATH=/home/mcambria/go2/src/stash.verizon.com/cni/bin
+export NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
+export CNI_COMMAND=DEL 
+export CNI_NETNS=/var/run/netns/mcc-cni-ipv6
+export CNI_CONTAINERID=mcc-cni-ipv6 
+
+export PATH=$CNI_PATH:$PATH
+export CNI_IFNAME=eth1 
+
+export MCCVAL="Jamal Env Value"
+
+vz-macvlan < 26-thing2-macvlan0.conf
+
+