From 29bdb1391889e940ea78f67516745cce49263dab Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Wed, 20 May 2015 10:30:49 -0700 Subject: [PATCH] stage1: add ipvlan network plugin This plugin is very similar to macvlan but let's the secondary interfaces use the primary interface's MAC address instead of generating their own one. This commit also updates the revisions for all Godeps from the appc/cni repository. --- Godeps/Godeps.json | 20 +- .../src/github.com/appc/cni/pkg/ip/link.go | 74 +++++--- .../github.com/appc/cni/pkg/plugin/ipam.go | 13 +- .../github.com/appc/cni/pkg/plugin/types.go | 6 + .../src/github.com/appc/cni/pkg/skel/skel.go | 22 ++- .../appc/cni/plugins/ipam/host-local/main.go | 6 +- .../appc/cni/plugins/main/bridge/bridge.go | 2 +- .../appc/cni/plugins/main/ipvlan/ipvlan.go | 177 ++++++++++++++++++ .../appc/cni/plugins/main/macvlan/macvlan.go | 6 +- .../appc/cni/plugins/main/veth/veth.go | 8 +- stage1/dummy.go | 1 + 11 files changed, 285 insertions(+), 50 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/appc/cni/plugins/main/ipvlan/ipvlan.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 6cfd362..e872bda 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -21,35 +21,39 @@ }, { "ImportPath": "github.com/appc/cni/pkg/ip", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/pkg/ns", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/pkg/plugin", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/pkg/skel", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/plugins/ipam/host-local", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/plugins/main/bridge", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" + }, + { + "ImportPath": "github.com/appc/cni/plugins/main/ipvlan", + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/plugins/main/macvlan", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/cni/plugins/main/veth", - "Rev": "88377fa3466eea30817b038c69ac06686e7655b7" + "Rev": "49a018fa85ac216fdc76c835371807bdcf3b8ead" }, { "ImportPath": "github.com/appc/docker2aci/lib", diff --git a/Godeps/_workspace/src/github.com/appc/cni/pkg/ip/link.go b/Godeps/_workspace/src/github.com/appc/cni/pkg/ip/link.go index e59e49d..c2ecef0 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/pkg/ip/link.go +++ b/Godeps/_workspace/src/github.com/appc/cni/pkg/ip/link.go @@ -15,7 +15,7 @@ package ip import ( - "crypto/sha512" + "crypto/rand" "fmt" "net" "os" @@ -23,7 +23,7 @@ import ( "github.com/coreos/rkt/Godeps/_workspace/src/github.com/vishvananda/netlink" ) -func makeVeth(name, peer string, mtu int) (netlink.Link, error) { +func makeVethPair(name, peer string, mtu int) (netlink.Link, error) { veth := &netlink.Veth{ LinkAttrs: netlink.LinkAttrs{ Name: name, @@ -39,33 +39,50 @@ func makeVeth(name, peer string, mtu int) (netlink.Link, error) { return veth, nil } +func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) { + for i := 0; i < 10; i++ { + peerName, err = RandomVethName() + if err != nil { + return + } + + veth, err = makeVethPair(name, peerName, mtu) + switch { + case err == nil: + return + + case os.IsExist(err): + continue + + default: + err = fmt.Errorf("failed to make veth pair: %v", err) + return + } + } + + // should really never be hit + err = fmt.Errorf("failed to find a unique veth name") + return +} + // RandomVethName returns string "veth" with random prefix (hashed from entropy) -func RandomVethName(entropy string) string { - h := sha512.New() - h.Write([]byte(entropy)) - return fmt.Sprintf("veth%x", h.Sum(nil)[:5]) +func RandomVethName() (string, error) { + entropy := make([]byte, 4) + _, err := rand.Reader.Read(entropy) + if err != nil { + return "", fmt.Errorf("failed to generate random veth name: %v", err) + } + + // NetworkManager (recent versions) will ignore veth devices that start with "veth" + return fmt.Sprintf("veth%x", entropy), nil } // SetupVeth sets up a virtual ethernet link. // Should be in container netns. -// TODO(eyakubovich): get rid of entropy and ask kernel to pick name via pattern -func SetupVeth(entropy, contVethName string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) { - // NetworkManager (recent versions) will ignore veth devices that start with "veth" - hostVethName := RandomVethName(entropy) - hostVeth, err = makeVeth(hostVethName, contVethName, mtu) +func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) { + var hostVethName string + hostVethName, contVeth, err = makeVeth(contVethName, mtu) if err != nil { - err = fmt.Errorf("failed to make veth pair: %v", err) - return - } - - if err = netlink.LinkSetUp(hostVeth); err != nil { - err = fmt.Errorf("failed to set %q up: %v", hostVethName, err) - return - } - - contVeth, err = netlink.LinkByName(contVethName) - if err != nil { - err = fmt.Errorf("failed to lookup %q: %v", contVethName, err) return } @@ -74,6 +91,17 @@ func SetupVeth(entropy, contVethName string, mtu int, hostNS *os.File) (hostVeth return } + hostVeth, err = netlink.LinkByName(hostVethName) + if err != nil { + err = fmt.Errorf("failed to lookup %q: %v", hostVethName, err) + return + } + + if err = netlink.LinkSetUp(hostVeth); err != nil { + err = fmt.Errorf("failed to set %q up: %v", contVethName, err) + return + } + if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil { err = fmt.Errorf("failed to move veth to host netns: %v", err) return diff --git a/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/ipam.go b/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/ipam.go index 4665011..9d0c0e3 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/ipam.go +++ b/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/ipam.go @@ -124,9 +124,18 @@ func ConfigureIface(ifName string, res *Result) error { return nil } -// PrintResult writes out prettified Result to stdout +// PrintResult writes out prettified Result JSON to stdout func PrintResult(res *Result) error { - data, err := json.MarshalIndent(res, "", " ") + return prettyPrint(res) +} + +// PrintError writes out prettified Error JSON to stdout +func PrintError(err *Error) error { + return prettyPrint(err) +} + +func prettyPrint(obj interface{}) error { + data, err := json.MarshalIndent(obj, "", " ") if err != nil { return err } diff --git a/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/types.go b/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/types.go index 428f0c7..15ab408 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/types.go +++ b/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin/types.go @@ -48,6 +48,12 @@ type Route struct { GW net.IP } +type Error struct { + Code uint `json:"code"` + Msg string `json:"msg"` + Details string `json:"details,omitempty"` +} + // net.IPNet is not JSON (un)marshallable so this duality is needed // for our custom ip.IPNet type diff --git a/Godeps/_workspace/src/github.com/appc/cni/pkg/skel/skel.go b/Godeps/_workspace/src/github.com/appc/cni/pkg/skel/skel.go index 9f03335..c370ba3 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/pkg/skel/skel.go +++ b/Godeps/_workspace/src/github.com/appc/cni/pkg/skel/skel.go @@ -17,9 +17,12 @@ package skel import ( + "fmt" "io/ioutil" "log" "os" + + "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin" ) // CmdArgs captures all the arguments passed in to the plugin @@ -61,13 +64,12 @@ func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) { } if argsMissing { - os.Exit(1) + die("required env variables missing") } stdinData, err := ioutil.ReadAll(os.Stdin) if err != nil { - log.Printf("Error reading from stdin: %v", err) - os.Exit(1) + die("error reading from stdin: %v", err) } cmdArgs := &CmdArgs{ @@ -87,12 +89,18 @@ func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) { err = cmdDel(cmdArgs) default: - log.Printf("Unknown CNI_COMMAND: %v", cmd) - os.Exit(1) + die("unknown CNI_COMMAND: %v", cmd) } if err != nil { - log.Printf("%v: %v", cmd, err) - os.Exit(1) + die(err.Error()) } } + +func die(f string, args ...interface{}) { + plugin.PrintError(&plugin.Error{ + Code: 100, + Msg: fmt.Sprintf(f, args...), + }) + os.Exit(1) +} diff --git a/Godeps/_workspace/src/github.com/appc/cni/plugins/ipam/host-local/main.go b/Godeps/_workspace/src/github.com/appc/cni/plugins/ipam/host-local/main.go index be07f06..b47b934 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/plugins/ipam/host-local/main.go +++ b/Godeps/_workspace/src/github.com/appc/cni/plugins/ipam/host-local/main.go @@ -47,9 +47,9 @@ func cmdAdd(args *skel.CmdArgs) error { switch ipamConf.Type { case "host-local": - ipConf, err = allocator.Get(args.Netns) + ipConf, err = allocator.Get(args.ContainerID) case "host-local-ptp": - ipConf, err = allocator.GetPtP(args.Netns) + ipConf, err = allocator.GetPtP(args.ContainerID) default: return errors.New("Unsupported IPAM plugin type") } @@ -80,5 +80,5 @@ func cmdDel(args *skel.CmdArgs) error { return err } - return allocator.Release(args.Netns) + return allocator.Release(args.ContainerID) } diff --git a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/bridge/bridge.go b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/bridge/bridge.go index 3544e7a..3cb4f0f 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/bridge/bridge.go +++ b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/bridge/bridge.go @@ -130,7 +130,7 @@ func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int, pr *plu err := ns.WithNetNSPath(netns, func(hostNS *os.File) error { // create the veth pair in the container and move host end into host netns - hostVeth, _, err := ip.SetupVeth(netns, ifName, mtu, hostNS) + hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) if err != nil { return err } diff --git a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/ipvlan/ipvlan.go b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/ipvlan/ipvlan.go new file mode 100644 index 0000000..1856954 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/ipvlan/ipvlan.go @@ -0,0 +1,177 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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" + "os" + "runtime" + + "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/pkg/ip" + "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/pkg/ns" + "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/pkg/plugin" + "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/pkg/skel" + "github.com/coreos/rkt/Godeps/_workspace/src/github.com/vishvananda/netlink" +) + +type NetConf struct { + plugin.NetConf + Master string `json:"master"` + Mode string `json:"mode"` + IPMasq bool `json:"ipMasq"` + 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, 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, nil +} + +func modeFromString(s string) (netlink.IPVlanMode, error) { + switch s { + case "": + return netlink.IPVLAN_MODE_L2, nil + case "l2": + return netlink.IPVLAN_MODE_L2, nil + case "l3": + return netlink.IPVLAN_MODE_L3, nil + default: + return 0, fmt.Errorf("unknown ipvlan mode: %q", s) + } +} + +func createIpvlan(conf *NetConf, ifName string, netns *os.File) error { + mode, err := modeFromString(conf.Mode) + if err != nil { + return err + } + + m, err := netlink.LinkByName(conf.Master) + if err != nil { + return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) + } + + mv := &netlink.IPVlan{ + LinkAttrs: netlink.LinkAttrs{ + MTU: conf.MTU, + Name: ifName, + ParentIndex: m.Attrs().Index, + Namespace: netlink.NsFd(int(netns.Fd())), + }, + Mode: mode, + } + + if err := netlink.LinkAdd(mv); err != nil { + return fmt.Errorf("failed to create ipvlan: %v", err) + } + + return err +} + +func cmdAdd(args *skel.CmdArgs) error { + n, err := loadConf(args.StdinData) + if err != nil { + return err + } + + netns, err := os.Open(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", netns, err) + } + defer netns.Close() + + tmpName, err := ip.RandomVethName() + if err != nil { + return err + } + + if err = createIpvlan(n, tmpName, netns); err != nil { + return err + } + + // run the IPAM plugin and get back the config to apply + result, err := plugin.ExecAdd(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + if result.IP4 == nil { + return errors.New("IPAM plugin returned missing IPv4 config") + } + + err = ns.WithNetNS(netns, func(_ *os.File) error { + err := renameLink(tmpName, args.IfName) + if err != nil { + return fmt.Errorf("failed to rename ipvlan to %q: %v", args.IfName, err) + } + + return plugin.ConfigureIface(args.IfName, result) + }) + if err != nil { + return err + } + + if n.IPMasq { + chain := "CNI-" + n.Name + if err = ip.SetupIPMasq(ip.Network(&result.IP4.IP), chain); err != nil { + return err + } + } + + return plugin.PrintResult(result) +} + +func cmdDel(args *skel.CmdArgs) error { + n, err := loadConf(args.StdinData) + if err != nil { + return err + } + + err = plugin.ExecDel(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } + + return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error { + return ip.DelLinkByName(args.IfName) + }) +} + +func renameLink(curName, newName string) error { + link, err := netlink.LinkByName(curName) + if err != nil { + return err + } + + return netlink.LinkSetName(link, newName) +} + +func main() { + skel.PluginMain(cmdAdd, cmdDel) +} diff --git a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/macvlan/macvlan.go b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/macvlan/macvlan.go index cbab389..d3603d1 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/macvlan/macvlan.go +++ b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/macvlan/macvlan.go @@ -111,7 +111,11 @@ func cmdAdd(args *skel.CmdArgs) error { } defer netns.Close() - tmpName := ip.RandomVethName(args.Netns) + tmpName, err := ip.RandomVethName() + if err != nil { + return err + } + if err = createMacvlan(n, tmpName, netns); err != nil { return err } diff --git a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/veth/veth.go b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/veth/veth.go index ddc26a2..5b094b2 100644 --- a/Godeps/_workspace/src/github.com/appc/cni/plugins/main/veth/veth.go +++ b/Godeps/_workspace/src/github.com/appc/cni/plugins/main/veth/veth.go @@ -46,9 +46,7 @@ type NetConf struct { func setupContainerVeth(netns, ifName string, mtu int, pr *plugin.Result) (string, error) { var hostVethName string err := ns.WithNetNSPath(netns, func(hostNS *os.File) error { - entropy := netns + ifName - - hostVeth, _, err := ip.SetupVeth(entropy, ifName, mtu, hostNS) + hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) if err != nil { return err } @@ -115,7 +113,7 @@ func cmdAdd(args *skel.CmdArgs) error { } if conf.IPMasq { - h := sha512.Sum512([]byte(args.Netns)) + h := sha512.Sum512([]byte(args.ContainerID)) chain := fmt.Sprintf("CNI-%s-%x", conf.Name, h[:8]) if err = ip.SetupIPMasq(&result.IP4.IP, chain); err != nil { return err @@ -142,7 +140,7 @@ func cmdDel(args *skel.CmdArgs) error { } if conf.IPMasq { - h := sha512.Sum512([]byte(args.Netns)) + h := sha512.Sum512([]byte(args.ContainerID)) chain := fmt.Sprintf("CNI-%s-%x", conf.Name, h[:8]) if err = ip.TeardownIPMasq(ipn, chain); err != nil { return err diff --git a/stage1/dummy.go b/stage1/dummy.go index 9accd51..2f1aaf4 100644 --- a/stage1/dummy.go +++ b/stage1/dummy.go @@ -11,6 +11,7 @@ import ( import ( _ "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/plugins/ipam/host-local" _ "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/plugins/main/bridge" + _ "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/plugins/main/ipvlan" _ "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/plugins/main/macvlan" _ "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/plugins/main/veth" )