
The project has been moved so internally we simply rename everything. Consumers are recommended to update their vendored version of cni.
265 lines
6.4 KiB
Go
265 lines
6.4 KiB
Go
// 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"
|
|
"os"
|
|
"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/utils"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
const defaultBrName = "cni0"
|
|
|
|
type NetConf struct {
|
|
types.NetConf
|
|
BrName string `json:"bridge"`
|
|
IsGW bool `json:"isGateway"`
|
|
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 loadNetConf(bytes []byte) (*NetConf, 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, nil
|
|
}
|
|
|
|
func ensureBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) 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
|
|
}
|
|
}
|
|
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 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,
|
|
},
|
|
}
|
|
|
|
if err := netlink.LinkAdd(br); err != nil {
|
|
if err != syscall.EEXIST {
|
|
return nil, fmt.Errorf("could not add %q: %v", brName, err)
|
|
}
|
|
|
|
// it's ok if the device already exists as long as config is similar
|
|
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 string, br *netlink.Bridge, ifName string, mtu int) error {
|
|
var hostVethName string
|
|
|
|
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
|
|
// create the veth pair in the container and move host end into host netns
|
|
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hostVethName = hostVeth.Attrs().Name
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// need to lookup hostVeth again as its index has changed during ns move
|
|
hostVeth, err := netlink.LinkByName(hostVethName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
|
|
}
|
|
|
|
// connect host veth end to the bridge
|
|
if err = netlink.LinkSetMaster(hostVeth, br); err != nil {
|
|
return fmt.Errorf("failed to connect %q to bridge %v: %v", hostVethName, br.Attrs().Name, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func calcGatewayIP(ipn *net.IPNet) net.IP {
|
|
nid := ipn.IP.Mask(ipn.Mask)
|
|
return ip.NextIP(nid)
|
|
}
|
|
|
|
func setupBridge(n *NetConf) (*netlink.Bridge, error) {
|
|
// create bridge if necessary
|
|
br, err := ensureBridge(n.BrName, n.MTU)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
|
|
}
|
|
|
|
return br, nil
|
|
}
|
|
|
|
func cmdAdd(args *skel.CmdArgs) error {
|
|
n, err := loadNetConf(args.StdinData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
br, err := setupBridge(n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = setupVeth(args.Netns, br, args.IfName, n.MTU); err != nil {
|
|
return err
|
|
}
|
|
|
|
// run the IPAM plugin and get back the config to apply
|
|
result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.IP4 == nil {
|
|
return errors.New("IPAM plugin returned missing IPv4 config")
|
|
}
|
|
|
|
if result.IP4.Gateway == nil && n.IsGW {
|
|
result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
|
|
}
|
|
|
|
err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
|
return ipam.ConfigureIface(args.IfName, result)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if n.IsGW {
|
|
gwn := &net.IPNet{
|
|
IP: result.IP4.Gateway,
|
|
Mask: result.IP4.IP.Mask,
|
|
}
|
|
|
|
if err = ensureBridgeAddr(br, gwn); 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)
|
|
if err = ip.SetupIPMasq(ip.Network(&result.IP4.IP), chain, comment); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
result.DNS = n.DNS
|
|
return result.Print()
|
|
}
|
|
|
|
func cmdDel(args *skel.CmdArgs) error {
|
|
n, err := loadNetConf(args.StdinData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var ipn *net.IPNet
|
|
err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
|
var err error
|
|
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
|
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 ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
|
}
|
|
|
|
func main() {
|
|
skel.PluginMain(cmdAdd, cmdDel)
|
|
}
|