Files
weave/plugin/net/cni.go
T
Martynas Pumputis ade6738789 Handle "weave expose" in weaver
The change makes "weave expose" to call the "/expose" HTTP
endpoint implemented in router which exposes the bridge.

Such architectural change required as we need to know whether NPC is
enabled when exposing the bridge and weaver knows this fact the best.

Unfortunately, this change makes impossible to "weave expose"
when weaver is not running.
2017-10-17 12:08:43 +01:00

210 lines
6.2 KiB
Go

package plugin
import (
"crypto/rand"
"encoding/json"
"fmt"
"net"
"os"
"github.com/containernetworking/cni/pkg/ipam"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
weaveapi "github.com/weaveworks/weave/api"
weavenet "github.com/weaveworks/weave/net"
ipamplugin "github.com/weaveworks/weave/plugin/ipam"
)
var (
zeroNetwork = net.IPNet{IP: net.IPv4zero, Mask: net.IPv4Mask(0, 0, 0, 0)}
mask32 = net.IPv4Mask(0xff, 0xff, 0xff, 0xff)
)
type CNIPlugin struct {
weave *weaveapi.Client
}
func NewCNIPlugin(weave *weaveapi.Client) *CNIPlugin {
return &CNIPlugin{weave: weave}
}
func loadNetConf(bytes []byte) (*NetConf, error) {
n := &NetConf{
BrName: weavenet.WeaveBridgeName,
HairpinMode: true,
}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}
return n, nil
}
func (c *CNIPlugin) getIP(ipamType string, args *skel.CmdArgs) (newResult *current.Result, err error) {
var result types.Result
// Default IPAM is Weave's own
if ipamType == "" {
result, err = ipamplugin.NewIpam(c.weave).Allocate(args)
} else {
result, err = ipam.ExecAdd(ipamType, args.StdinData)
}
if err != nil {
return nil, err
}
if result == nil {
return nil, fmt.Errorf("Received no usable result from IPAM plugin")
}
newResult, err = current.NewResultFromResult(result)
// Check if ipam returned no results without error
if err == nil && len(newResult.IPs) == 0 {
return nil, fmt.Errorf("IPAM plugin failed to allocate IP address")
}
return newResult, err
}
func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
conf, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
if conf.IsGW {
return fmt.Errorf("Gateway functionality not supported")
}
if conf.IPMasq {
return fmt.Errorf("IP Masquerading functionality not supported")
}
result, err := c.getIP(conf.IPAM.Type, args)
if err != nil {
return fmt.Errorf("unable to allocate IP address: %s", err)
}
// Only expecting one address
ip := result.IPs[0]
// If config says nothing about routes or gateway, default one will be via the bridge
if len(result.Routes) == 0 && ip.Gateway == nil {
bridgeIP, err := weavenet.FindBridgeIP(conf.BrName, &ip.Address)
if err == weavenet.ErrBridgeNoIP {
bridgeArgs := *args
bridgeArgs.ContainerID = "weave:expose"
// It would be better if libcni let us send just the desired parameters,
// but there is a bug: https://github.com/containernetworking/cni/issues/410
// so just blank out the one we want to change
os.Setenv("CNI_CONTAINERID", bridgeArgs.ContainerID)
bridgeIPResult, err := c.getIP(conf.IPAM.Type, &bridgeArgs)
if err != nil {
return fmt.Errorf("unable to allocate IP address for bridge: %s", err)
}
bridgeCIDR := bridgeIPResult.IPs[0].Address
if err := c.weave.Expose(&bridgeCIDR); err != nil {
return fmt.Errorf("unable to expose bridge %q: %s", conf.BrName, err)
}
bridgeIP = bridgeCIDR.IP
} else if err != nil {
return err
}
result.IPs[0].Gateway = bridgeIP
}
ns, err := netns.GetFromPath(args.Netns)
if err != nil {
return fmt.Errorf("error accessing namespace %q: %s", args.Netns, err)
}
defer ns.Close()
id := args.ContainerID
if len(id) < 5 {
data := make([]byte, 5)
_, err := rand.Reader.Read(data)
if err != nil {
return err
}
id = fmt.Sprintf("%x", data)
}
if err := weavenet.AttachContainer(args.Netns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&ip.Address}, false, conf.HairpinMode); err != nil {
return err
}
if err := weavenet.WithNetNSLinkUnsafe(ns, args.IfName, func(link netlink.Link) error {
return setupRoutes(link, args.IfName, ip.Address, ip.Gateway, result.Routes)
}); err != nil {
return fmt.Errorf("error setting up routes: %s", err)
}
result.DNS = conf.DNS
return types.PrintResult(result, conf.CNIVersion)
}
func setupRoutes(link netlink.Link, name string, ipnet net.IPNet, gw net.IP, routes []*types.Route) error {
var err error
if routes == nil { // If config says nothing about routes, add a default one
if !ipnet.Contains(gw) {
// The bridge IP is not on the same subnet; add a specific route to it
gw32 := &net.IPNet{IP: gw, Mask: mask32}
if err = weavenet.AddRoute(link, netlink.SCOPE_LINK, gw32, nil); err != nil {
return err
}
}
routes = []*types.Route{{Dst: zeroNetwork}}
}
for _, r := range routes {
if r.GW != nil {
err = weavenet.AddRoute(link, netlink.SCOPE_UNIVERSE, &r.Dst, r.GW)
} else {
err = weavenet.AddRoute(link, netlink.SCOPE_UNIVERSE, &r.Dst, gw)
}
if err != nil {
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, name, err)
}
}
return nil
}
// As of CNI 0.5 spec:
// "Plugins should generally complete a DEL action without error even if some resources are missing"
// this method should therefore return nil in most, if not all, cases.
func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error {
conf, err := loadNetConf(args.StdinData)
if err != nil {
logOnStderr(err)
return nil
}
// As of CNI 0.3 spec, runtimes can send blank if they just want the address deallocated
if args.Netns != "" {
if _, err = weavenet.WithNetNS(args.Netns, "del-iface", args.IfName); err != nil {
// We log the error and carry on instead of returning nil, as there may still be resources to free in IPAM.
logOnStderr(fmt.Errorf("error removing interface %q: %s", args.IfName, err))
}
}
// Default IPAM is Weave's own
if conf.IPAM.Type == "" {
err = ipamplugin.NewIpam(c.weave).Release(args)
} else {
err = ipam.ExecDel(conf.IPAM.Type, args.StdinData)
}
logOnStderr(fmt.Errorf("unable to release IP address: %s", err))
return nil
}
// Standard error is inherited from the caller (e.g. Kubelet, when running Weave Net's CNI plugin
// under Kubernetes) hence provided errors should eventually appear in the caller's log files.
func logOnStderr(err error) {
fmt.Fprintln(os.Stderr, "weave-cni:", err)
}
type NetConf struct {
types.NetConf
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"`
}