From: Michael Cambria Date: Thu, 9 Mar 2017 21:31:34 +0000 (-0500) Subject: Initial IPv6 pass X-Git-Url: https://git.halfball.org/?a=commitdiff_plain;h=f01c8de9c51ab031d0e451c358848f951328d6b7;p=cni.git Initial IPv6 pass --- diff --git a/16-mynet.conf b/16-mynet.conf new file mode 100644 index 0000000..8beabd3 --- /dev/null +++ b/16-mynet.conf @@ -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" } + ] + } +} + diff --git a/22-thing2-macvlan0.conf b/22-thing2-macvlan0.conf index f52e7ed..8eb199e 100644 --- a/22-thing2-macvlan0.conf +++ b/22-thing2-macvlan0.conf @@ -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 index 0000000..fe91f92 --- /dev/null +++ b/26-thing2-macvlan0.conf @@ -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" } + ] + } +} + diff --git a/mcc-manual-add.sh b/mcc-manual-add.sh index 846ba8e..b74ddfe 100755 --- a/mcc-manual-add.sh +++ b/mcc-manual-add.sh @@ -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 diff --git a/mcc-manual-del.sh b/mcc-manual-del.sh index c3af64e..c323c5a 100755 --- a/mcc-manual-del.sh +++ b/mcc-manual-del.sh @@ -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 index 0000000..5122d9d --- /dev/null +++ b/mcc-six-add.sh @@ -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 index 0000000..6057af7 --- /dev/null +++ b/mcc-six-del.sh @@ -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 + + diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go index 457fe5b..608bbc0 100644 --- a/pkg/ipam/ipam.go +++ b/pkg/ipam/ipam.go @@ -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 */ diff --git a/plugins/ipam/vz-local/backend/allocator/allocator.go b/plugins/ipam/vz-local/backend/allocator/allocator.go index 64b685d..db3ffda 100644 --- a/plugins/ipam/vz-local/backend/allocator/allocator.go +++ b/plugins/ipam/vz-local/backend/allocator/allocator.go @@ -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 := ¤t.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 := ¤t.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 index 0000000..0388d12 --- /dev/null +++ b/plugins/main/vz-bridge/bridge.go @@ -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 := ¤t.Interface{} + hostIface := ¤t.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, ¤t.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 index 0000000..9aa214e --- /dev/null +++ b/plugins/main/vz-bridge/bridge_suite_test.go @@ -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 index 0000000..5a8fd5a --- /dev/null +++ b/plugins/main/vz-bridge/bridge_test.go @@ -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 index 0000000..0409c04 --- /dev/null +++ b/plugins/main/vz-macvlan/macvlan.go @@ -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 := ¤t.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 index 0000000..844e567 --- /dev/null +++ b/plugins/main/vz-macvlan/macvlan_suite_test.go @@ -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 index 0000000..dfb4e8c --- /dev/null +++ b/plugins/main/vz-macvlan/macvlan_test.go @@ -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 index 0000000..3aad1f0 --- /dev/null +++ b/thing2-six-add.sh @@ -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 index 0000000..2b8ae56 --- /dev/null +++ b/thing2-six-del.sh @@ -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 + +