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.
This commit is contained in:
Stefan Junker
2015-05-20 11:19:59 -07:00
parent 2e2be20477
commit 29bdb13918
11 changed files with 285 additions and 50 deletions
+12 -8
View File
@@ -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",
+51 -23
View File
@@ -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
+11 -2
View File
@@ -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
}
+6
View File
@@ -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
+15 -7
View File
@@ -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)
}
+3 -3
View File
@@ -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)
}
+1 -1
View File
@@ -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
}
+177
View File
@@ -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)
}
+5 -1
View File
@@ -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
}
+3 -5
View File
@@ -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
+1
View File
@@ -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"
)