Add plugin code
This adds basic plugins. "main" types: veth, bridge, macvlan "ipam" type: host-local The code has been ported over from github.com/coreos/rkt project and adapted to fit the CNI spec.
This commit is contained in:
commit
c24708ff62
86
ip/cidr.go
Normal file
86
ip/cidr.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 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 ip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseCIDR takes a string like "10.2.3.1/24" and
|
||||||
|
// return IPNet with "10.2.3.1" and /24 mask
|
||||||
|
func ParseCIDR(s string) (*net.IPNet, error) {
|
||||||
|
ip, ipn, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipn.IP = ip
|
||||||
|
return ipn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextIP returns IP incremented by 1
|
||||||
|
func NextIP(ip net.IP) net.IP {
|
||||||
|
i := ipToInt(ip)
|
||||||
|
return intToIP(i.Add(i, big.NewInt(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrevIP returns IP decremented by 1
|
||||||
|
func PrevIP(ip net.IP) net.IP {
|
||||||
|
i := ipToInt(ip)
|
||||||
|
return intToIP(i.Sub(i, big.NewInt(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipToInt(ip net.IP) *big.Int {
|
||||||
|
if v := ip.To4(); v != nil {
|
||||||
|
return big.NewInt(0).SetBytes(v)
|
||||||
|
}
|
||||||
|
return big.NewInt(0).SetBytes(ip.To16())
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToIP(i *big.Int) net.IP {
|
||||||
|
return net.IP(i.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network masks off the host portion of the IP
|
||||||
|
func Network(ipn *net.IPNet) *net.IPNet {
|
||||||
|
return &net.IPNet{
|
||||||
|
IP: ipn.IP.Mask(ipn.Mask),
|
||||||
|
Mask: ipn.Mask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like net.IPNet but adds JSON marshalling and unmarshalling
|
||||||
|
type IPNet net.IPNet
|
||||||
|
|
||||||
|
func (n IPNet) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal((*net.IPNet)(&n).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *IPNet) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp, err := ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*n = IPNet(*tmp)
|
||||||
|
return nil
|
||||||
|
}
|
66
ip/ipmasq.go
Normal file
66
ip/ipmasq.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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 ip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/appc/cni/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupIPMasq installs iptables rules to masquerade traffic
|
||||||
|
// coming from ipn and going outside of it
|
||||||
|
func SetupIPMasq(ipn *net.IPNet, chain string) error {
|
||||||
|
ipt, err := iptables.New()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to locate iptabes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ipt.NewChain("nat", chain); err != nil {
|
||||||
|
if err.(*iptables.Error).ExitStatus() != 1 {
|
||||||
|
// TODO(eyakubovich): assumes exit status 1 implies chain exists
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ipt.AppendUnique("nat", chain, "!", "-d", "224.0.0.0/4", "-j", "MASQUERADE"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeardownIPMasq undoes the effects of SetupIPMasq
|
||||||
|
func TeardownIPMasq(ipn *net.IPNet, chain string) error {
|
||||||
|
ipt, err := iptables.New()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to locate iptabes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ipt.ClearChain("nat", chain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipt.DeleteChain("nat", chain)
|
||||||
|
}
|
117
ip/link.go
Normal file
117
ip/link.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// 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 ip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/appc/cni/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeVeth(name, peer string, mtu int) (netlink.Link, error) {
|
||||||
|
veth := &netlink.Veth{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: name,
|
||||||
|
Flags: net.FlagUp,
|
||||||
|
MTU: mtu,
|
||||||
|
},
|
||||||
|
PeerName: peer,
|
||||||
|
}
|
||||||
|
if err := netlink.LinkAdd(veth); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return veth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = netlink.LinkSetUp(contVeth); 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLinkByName removes an interface link.
|
||||||
|
func DelLinkByName(ifName string) error {
|
||||||
|
iface, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = netlink.LinkDel(iface); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLinkByNameAddr remove an interface returns its IP address
|
||||||
|
// of the specified family
|
||||||
|
func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
|
||||||
|
iface, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := netlink.AddrList(iface, family)
|
||||||
|
if err != nil || len(addrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = netlink.LinkDel(iface); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs[0].IPNet, nil
|
||||||
|
}
|
47
ip/route.go
Normal file
47
ip/route.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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 ip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/appc/cni/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDefaultRoute sets the default route on the given gateway.
|
||||||
|
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||||
|
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
||||||
|
return AddRoute(defNet, gw, dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRoute adds a universally-scoped route to a device.
|
||||||
|
func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||||
|
return netlink.RouteAdd(&netlink.Route{
|
||||||
|
LinkIndex: dev.Attrs().Index,
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Dst: ipn,
|
||||||
|
Gw: gw,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHostRoute adds a host-scoped route to a device.
|
||||||
|
func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||||
|
return netlink.RouteAdd(&netlink.Route{
|
||||||
|
LinkIndex: dev.Attrs().Index,
|
||||||
|
Scope: netlink.SCOPE_HOST,
|
||||||
|
Dst: ipn,
|
||||||
|
Gw: gw,
|
||||||
|
})
|
||||||
|
}
|
81
ns/ns.go
Normal file
81
ns/ns.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// 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 ns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var setNsMap = map[string]uintptr{
|
||||||
|
"386": 346,
|
||||||
|
"amd64": 308,
|
||||||
|
"arm": 374,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNS sets the network namespace on a target file.
|
||||||
|
func SetNS(f *os.File, flags uintptr) error {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
trap, ok := setNsMap[runtime.GOARCH]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unsupported arch: %s", runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := syscall.RawSyscall(trap, f.Fd(), flags, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNetNSPath executes the passed closure under the given network
|
||||||
|
// namespace, restoring the original namespace afterwards.
|
||||||
|
func WithNetNSPath(nspath string, f func(*os.File) error) error {
|
||||||
|
ns, err := os.Open(nspath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to open %v: %v", nspath, err)
|
||||||
|
}
|
||||||
|
defer ns.Close()
|
||||||
|
|
||||||
|
return WithNetNS(ns, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNetNS executes the passed closure under the given network
|
||||||
|
// namespace, restoring the original namespace afterwards.
|
||||||
|
func WithNetNS(ns *os.File, f func(*os.File) error) error {
|
||||||
|
// save a handle to current (host) network namespace
|
||||||
|
thisNS, err := os.Open("/proc/self/ns/net")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to open /proc/self/ns/net: %v", err)
|
||||||
|
}
|
||||||
|
defer thisNS.Close()
|
||||||
|
|
||||||
|
if err = SetNS(ns, syscall.CLONE_NEWNET); err != nil {
|
||||||
|
return fmt.Errorf("Error switching to ns %v: %v", ns.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = f(thisNS); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch back
|
||||||
|
return SetNS(thisNS, syscall.CLONE_NEWNET)
|
||||||
|
}
|
136
plugin/ipam.go
Normal file
136
plugin/ipam.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/appc/cni/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||||
|
"github.com/appc/cni/pkg/ip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find returns the full path of the plugin by searching in CNI_PATH
|
||||||
|
func Find(plugin string) string {
|
||||||
|
paths := strings.Split(os.Getenv("CNI_PATH"), ":")
|
||||||
|
|
||||||
|
for _, p := range paths {
|
||||||
|
fullname := filepath.Join(p, plugin)
|
||||||
|
if fi, err := os.Stat(fullname); err == nil && fi.Mode().IsRegular() {
|
||||||
|
return fullname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecAdd executes IPAM plugin, assuming CNI_COMMAND == ADD.
|
||||||
|
// Parses and returns resulting IPConfig
|
||||||
|
func ExecAdd(plugin string, netconf []byte) (*Result, error) {
|
||||||
|
if os.Getenv("CNI_COMMAND") != "ADD" {
|
||||||
|
return nil, fmt.Errorf("CNI_COMMAND is not ADD")
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginPath := Find(plugin)
|
||||||
|
if pluginPath == "" {
|
||||||
|
return nil, fmt.Errorf("could not find %q plugin", plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout := &bytes.Buffer{}
|
||||||
|
|
||||||
|
c := exec.Cmd{
|
||||||
|
Path: pluginPath,
|
||||||
|
Args: []string{pluginPath},
|
||||||
|
Stdin: bytes.NewBuffer(netconf),
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &Result{}
|
||||||
|
err := json.Unmarshal(stdout.Bytes(), res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecDel executes IPAM plugin, assuming CNI_COMMAND == DEL.
|
||||||
|
func ExecDel(plugin string, netconf []byte) error {
|
||||||
|
if os.Getenv("CNI_COMMAND") != "DEL" {
|
||||||
|
return fmt.Errorf("CNI_COMMAND is not DEL")
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginPath := Find(plugin)
|
||||||
|
if pluginPath == "" {
|
||||||
|
return fmt.Errorf("could not find %q plugin", plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := exec.Cmd{
|
||||||
|
Path: pluginPath,
|
||||||
|
Args: []string{pluginPath},
|
||||||
|
Stdin: bytes.NewBuffer(netconf),
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
return c.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureIface takes the result of IPAM plugin and
|
||||||
|
// applies to the ifName interface
|
||||||
|
func ConfigureIface(ifName string, res *Result) error {
|
||||||
|
link, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(link); err != nil {
|
||||||
|
return fmt.Errorf("failed too set %q UP: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(eyakubovich): IPv6
|
||||||
|
addr := &netlink.Addr{IPNet: &res.IP4.IP, Label: ""}
|
||||||
|
if err = netlink.AddrAdd(link, addr); err != nil {
|
||||||
|
return fmt.Errorf("failed to add IP addr to %q: %v", ifName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range res.IP4.Routes {
|
||||||
|
gw := r.GW
|
||||||
|
if gw == nil {
|
||||||
|
gw = res.IP4.Gateway
|
||||||
|
}
|
||||||
|
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
|
||||||
|
// we skip over duplicate routes as we assume the first one wins
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintResult writes out prettified Result to stdout
|
||||||
|
func PrintResult(res *Result) error {
|
||||||
|
data, err := json.MarshalIndent(res, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
106
plugin/types.go
Normal file
106
plugin/types.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// 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 plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/appc/cni/pkg/ip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetConf describes a network.
|
||||||
|
type NetConf struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
IPAM struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
} `json:"ipam,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||||
|
type Result struct {
|
||||||
|
IP4 *IPConfig `json:"ip4,omitempty"`
|
||||||
|
IP6 *IPConfig `json:"ip6,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPConfig contains values necessary to configure an interface
|
||||||
|
type IPConfig struct {
|
||||||
|
IP net.IPNet
|
||||||
|
Gateway net.IP
|
||||||
|
Routes []Route
|
||||||
|
}
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Dst net.IPNet
|
||||||
|
GW net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||||
|
// for our custom ip.IPNet type
|
||||||
|
|
||||||
|
// JSON (un)marshallable types
|
||||||
|
type ipConfig struct {
|
||||||
|
IP ip.IPNet `json:"ip"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty"`
|
||||||
|
Routes []Route `json:"routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type route struct {
|
||||||
|
Dst ip.IPNet `json:"dst"`
|
||||||
|
GW net.IP `json:"gw,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
ipc := ipConfig{
|
||||||
|
IP: ip.IPNet(c.IP),
|
||||||
|
Gateway: c.Gateway,
|
||||||
|
Routes: c.Routes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(ipc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
||||||
|
ipc := ipConfig{}
|
||||||
|
if err := json.Unmarshal(data, &ipc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.IP = net.IPNet(ipc.IP)
|
||||||
|
c.Gateway = ipc.Gateway
|
||||||
|
c.Routes = ipc.Routes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) UnmarshalJSON(data []byte) error {
|
||||||
|
rt := route{}
|
||||||
|
if err := json.Unmarshal(data, &rt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Dst = net.IPNet(rt.Dst)
|
||||||
|
r.GW = rt.GW
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) MarshalJSON() ([]byte, error) {
|
||||||
|
rt := route{
|
||||||
|
Dst: ip.IPNet(r.Dst),
|
||||||
|
GW: r.GW,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(rt)
|
||||||
|
}
|
98
skel/skel.go
Normal file
98
skel/skel.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2014 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 skel provides skeleton code for a CNI plugin.
|
||||||
|
// In particular, it implements argument parsing and validation.
|
||||||
|
package skel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdArgs captures all the arguments passed in to the plugin
|
||||||
|
// via both env vars and stdin
|
||||||
|
type CmdArgs struct {
|
||||||
|
ContainerID string
|
||||||
|
Netns string
|
||||||
|
IfName string
|
||||||
|
Args string
|
||||||
|
Path string
|
||||||
|
StdinData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginMain is the "main" for a plugin. It accepts
|
||||||
|
// two callback functions for add and del commands.
|
||||||
|
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
|
||||||
|
var cmd, contID, netns, ifName, args, path string
|
||||||
|
|
||||||
|
vars := []struct {
|
||||||
|
name string
|
||||||
|
val *string
|
||||||
|
req bool
|
||||||
|
}{
|
||||||
|
{"CNI_COMMAND", &cmd, true},
|
||||||
|
{"CNI_CONTAINERID", &contID, false},
|
||||||
|
{"CNI_NETNS", &netns, true},
|
||||||
|
{"CNI_IFNAME", &ifName, true},
|
||||||
|
{"CNI_ARGS", &args, false},
|
||||||
|
{"CNI_PATH", &path, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
argsMissing := false
|
||||||
|
for _, v := range vars {
|
||||||
|
*v.val = os.Getenv(v.name)
|
||||||
|
if v.req && *v.val == "" {
|
||||||
|
log.Printf("%v env variable missing", v.name)
|
||||||
|
argsMissing = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if argsMissing {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdinData, err := ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading from stdin: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdArgs := &CmdArgs{
|
||||||
|
ContainerID: contID,
|
||||||
|
Netns: netns,
|
||||||
|
IfName: ifName,
|
||||||
|
Args: args,
|
||||||
|
Path: path,
|
||||||
|
StdinData: stdinData,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "ADD":
|
||||||
|
err = cmdAdd(cmdArgs)
|
||||||
|
|
||||||
|
case "DEL":
|
||||||
|
err = cmdDel(cmdArgs)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Printf("Unknown CNI_COMMAND: %v", cmd)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%v: %v", cmd, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user