Merge branch 'master' into noErrorEndpointNotFound
This commit is contained in:
@ -18,7 +18,7 @@ $ ./dhcp daemon
|
||||
|
||||
If given `-pidfile <path>` arguments after 'daemon', the dhcp plugin will write
|
||||
its PID to the given file.
|
||||
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for netns as `<prefix>/<original netns>`. It could be used in case of running dhcp daemon as container.
|
||||
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for DHCP socket as `<prefix>/run/cni/dhcp.sock`. You can use this prefix for references to the host filesystem, e.g. to access netns and the unix socket.
|
||||
|
||||
Alternatively, you can use systemd socket activation protocol.
|
||||
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
|
||||
|
@ -172,7 +172,7 @@ func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
l, err := getListener(socketPath)
|
||||
l, err := getListener(hostPrefix + socketPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting listener: %v", err)
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ func (l *DHCPLease) acquire() error {
|
||||
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
|
||||
opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter)}
|
||||
opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter), byte(dhcp4.OptionSubnetMask)}
|
||||
|
||||
pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRequest(c, opts)
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const defaultSocketPath = "/run/cni/dhcp.sock"
|
||||
@ -38,7 +39,7 @@ func main() {
|
||||
var socketPath string
|
||||
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
|
||||
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
|
||||
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to netns")
|
||||
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root")
|
||||
daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath")
|
||||
daemonFlags.Parse(os.Args[2:])
|
||||
|
||||
@ -51,8 +52,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dhcp"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,9 +80,23 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
//return fmt.Errorf("not implemented")
|
||||
// Plugin must return result in same version as specified in netconf
|
||||
versionDecoder := &version.ConfigDecoder{}
|
||||
//confVersion, err := versionDecoder.Decode(args.StdinData)
|
||||
_, err := versionDecoder.Decode(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := ¤t.Result{}
|
||||
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SocketPathConf struct {
|
||||
|
@ -98,7 +98,7 @@ func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
|
||||
opt = opt[octets+5 : len(opt)]
|
||||
opt = opt[octets+5:]
|
||||
}
|
||||
}
|
||||
return routes
|
||||
|
@ -24,14 +24,14 @@ import (
|
||||
|
||||
func validateRoutes(t *testing.T, routes []*types.Route) {
|
||||
expected := []*types.Route{
|
||||
&types.Route{
|
||||
{
|
||||
Dst: net.IPNet{
|
||||
IP: net.IPv4(10, 0, 0, 0),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
},
|
||||
GW: net.IPv4(10, 1, 2, 3),
|
||||
},
|
||||
&types.Route{
|
||||
{
|
||||
Dst: net.IPNet{
|
||||
IP: net.IPv4(192, 168, 1, 0),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
|
11
plugins/ipam/dhcp/systemd/cni-dhcp.service
Normal file
11
plugins/ipam/dhcp/systemd/cni-dhcp.service
Normal file
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=CNI DHCP service
|
||||
Documentation=https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp
|
||||
After=network.target cni-dhcp.socket
|
||||
Requires=cni-dhcp.socket
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/cni/bin/dhcp daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
14
plugins/ipam/dhcp/systemd/cni-dhcp.socket
Normal file
14
plugins/ipam/dhcp/systemd/cni-dhcp.socket
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=CNI DHCP service socket
|
||||
Documentation=https://github.com/containernetworking/plugins/tree/master/plugins/ipam/dhcp
|
||||
PartOf=cni-dhcp.service
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/cni/dhcp.sock
|
||||
SocketMode=0660
|
||||
SocketUser=root
|
||||
SocketGroup=root
|
||||
RemoveOnStop=true
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
@ -97,7 +97,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
n.IPAM.IPArgs = append(n.IPAM.IPArgs, n.Args.A.IPs...)
|
||||
}
|
||||
|
||||
for idx, _ := range n.IPAM.IPArgs {
|
||||
for idx := range n.IPAM.IPArgs {
|
||||
if err := canonicalizeIP(&n.IPAM.IPArgs[idx]); err != nil {
|
||||
return nil, "", fmt.Errorf("cannot understand ip: %v", err)
|
||||
}
|
||||
@ -122,7 +122,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
// Validate all ranges
|
||||
numV4 := 0
|
||||
numV6 := 0
|
||||
for i, _ := range n.IPAM.Ranges {
|
||||
for i := range n.IPAM.Ranges {
|
||||
if err := n.IPAM.Ranges[i].Canonicalize(); err != nil {
|
||||
return nil, "", fmt.Errorf("invalid range set %d: %s", i, err)
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ var _ = Describe("IPAM config", func() {
|
||||
Name: "mynet",
|
||||
Type: "host-local",
|
||||
Ranges: []RangeSet{
|
||||
RangeSet{
|
||||
{
|
||||
{
|
||||
RangeStart: net.IP{10, 1, 2, 9},
|
||||
RangeEnd: net.IP{10, 1, 2, 20},
|
||||
|
@ -61,7 +61,7 @@ func (s *RangeSet) Canonicalize() error {
|
||||
}
|
||||
|
||||
fam := 0
|
||||
for i, _ := range *s {
|
||||
for i := range *s {
|
||||
if err := (*s)[i].Canonicalize(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -98,6 +98,43 @@ func (s *Store) Release(ip net.IP) error {
|
||||
return os.Remove(GetEscapedPath(s.dataDir, ip.String()))
|
||||
}
|
||||
|
||||
func (s *Store) FindByKey(id string, ifname string, match string) (bool, error) {
|
||||
found := false
|
||||
|
||||
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(string(data)) == match {
|
||||
found = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return found, err
|
||||
|
||||
}
|
||||
|
||||
func (s *Store) FindByID(id string, ifname string) bool {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
found := false
|
||||
match := strings.TrimSpace(id) + LineBreak + ifname
|
||||
found, err := s.FindByKey(id, ifname, match)
|
||||
|
||||
// Match anything created by this id
|
||||
if !found && err == nil {
|
||||
match := strings.TrimSpace(id)
|
||||
found, err = s.FindByKey(id, ifname, match)
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) {
|
||||
found := false
|
||||
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
|
||||
|
@ -15,10 +15,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
|
||||
|
||||
@ -29,13 +31,38 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
|
||||
n := &types.NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Look to see if there is at least one IP address allocated to the container
|
||||
// in the data dir, irrespective of what that address actually is
|
||||
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
containerIpFound := store.FindByID(args.ContainerID, args.IfName)
|
||||
if containerIpFound == false {
|
||||
return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
@ -22,10 +22,10 @@ import (
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// The top-level network config - IPAM plugins are passed the full configuration
|
||||
@ -58,13 +58,59 @@ type Address struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("static"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
|
||||
n := &types.NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
ipamConf, _, err := LoadIPAMConfig(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get PrevResult from stdin... store in RawPrevResult
|
||||
n, _, err := loadNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if n.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(n); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := current.NewResultFromResult(n.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Each configured IP should be found in result.IPs
|
||||
for _, rangeset := range ipamConf.Addresses {
|
||||
for _, ips := range result.IPs {
|
||||
// Ensure values are what we expect
|
||||
if rangeset.Address.IP.Equal(ips.Address.IP) {
|
||||
if rangeset.Gateway == nil {
|
||||
break
|
||||
} else if rangeset.Gateway.Equal(ips.Gateway) {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("static: Failed to match addr %v on interface %v", ips.Address.IP, args.IfName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// canonicalizeIP makes sure a provided ip is in standard form
|
||||
|
12
plugins/linux_only.txt
Normal file
12
plugins/linux_only.txt
Normal file
@ -0,0 +1,12 @@
|
||||
plugins/ipam/dhcp
|
||||
plugins/main/bridge
|
||||
plugins/main/host-device
|
||||
plugins/main/ipvlan
|
||||
plugins/main/loopback
|
||||
plugins/main/macvlan
|
||||
plugins/main/ptp
|
||||
plugins/main/vlan
|
||||
plugins/meta/portmap
|
||||
plugins/meta/tuning
|
||||
plugins/meta/bandwidth
|
||||
plugins/meta/firewall
|
@ -52,3 +52,8 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
|
||||
* `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For L2-only network, create empty dictionary.
|
||||
* `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false.
|
||||
* `vlan` (int, optional): assign VLAN tag. Defaults to none.
|
||||
|
||||
*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface.
|
||||
|
||||
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.
|
@ -18,12 +18,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"io/ioutil"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -33,8 +35,7 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// For testcases to force an error after IPAM has been performed
|
||||
@ -52,6 +53,7 @@ type NetConf struct {
|
||||
MTU int `json:"mtu"`
|
||||
HairpinMode bool `json:"hairpinMode"`
|
||||
PromiscMode bool `json:"promiscMode"`
|
||||
Vlan int `json:"vlan"`
|
||||
}
|
||||
|
||||
type gwInfo struct {
|
||||
@ -144,7 +146,7 @@ func calcGateways(result *current.Result, n *NetConf) (*gwInfo, *gwInfo, error)
|
||||
return gwsV4, gwsV6, nil
|
||||
}
|
||||
|
||||
func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddress bool) error {
|
||||
func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool) error {
|
||||
addrs, err := netlink.AddrList(br, family)
|
||||
if err != nil && err != syscall.ENOENT {
|
||||
return fmt.Errorf("could not get list of IP addresses: %v", err)
|
||||
@ -164,34 +166,34 @@ func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddre
|
||||
// forceAddress is true, otherwise throw an error.
|
||||
if family == netlink.FAMILY_V4 || a.IPNet.Contains(ipn.IP) || ipn.Contains(a.IPNet.IP) {
|
||||
if forceAddress {
|
||||
if err = deleteBridgeAddr(br, a.IPNet); err != nil {
|
||||
if err = deleteAddr(br, a.IPNet); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("%q already has an IP address different from %v", br.Name, ipnStr)
|
||||
return fmt.Errorf("%q already has an IP address different from %v", br.Attrs().Name, ipnStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
|
||||
}
|
||||
|
||||
// Set the bridge's MAC to itself. Otherwise, the bridge will take the
|
||||
// lowest-numbered mac on the bridge, and will change as ifs churn
|
||||
if err := netlink.LinkSetHardwareAddr(br, br.HardwareAddr); err != nil {
|
||||
if err := netlink.LinkSetHardwareAddr(br, br.Attrs().HardwareAddr); err != nil {
|
||||
return fmt.Errorf("could not set bridge's mac: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteBridgeAddr(br *netlink.Bridge, ipn *net.IPNet) error {
|
||||
func deleteAddr(br netlink.Link, ipn *net.IPNet) error {
|
||||
addr := &netlink.Addr{IPNet: ipn, Label: ""}
|
||||
|
||||
if err := netlink.AddrDel(br, addr); err != nil {
|
||||
return fmt.Errorf("could not remove IP address from %q: %v", br.Name, err)
|
||||
return fmt.Errorf("could not remove IP address from %q: %v", br.Attrs().Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -209,7 +211,7 @@ func bridgeByName(name string) (*netlink.Bridge, error) {
|
||||
return br, nil
|
||||
}
|
||||
|
||||
func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, error) {
|
||||
func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) {
|
||||
br := &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: brName,
|
||||
@ -220,6 +222,7 @@ func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, er
|
||||
// default packet limit
|
||||
TxQLen: -1,
|
||||
},
|
||||
VlanFiltering: &vlanFiltering,
|
||||
}
|
||||
|
||||
err := netlink.LinkAdd(br)
|
||||
@ -247,7 +250,35 @@ func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, er
|
||||
return br, nil
|
||||
}
|
||||
|
||||
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) (*current.Interface, *current.Interface, error) {
|
||||
func ensureVlanInterface(br *netlink.Bridge, vlanId int) (netlink.Link, error) {
|
||||
name := fmt.Sprintf("%s.%d", br.Name, vlanId)
|
||||
|
||||
brGatewayVeth, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
if err.Error() != "Link not found" {
|
||||
return nil, fmt.Errorf("failed to find interface %q: %v", name, err)
|
||||
}
|
||||
|
||||
hostNS, err := ns.GetCurrentNS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild to find host namespace: %v", err)
|
||||
}
|
||||
|
||||
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
|
||||
}
|
||||
|
||||
brGatewayVeth, err = netlink.LinkByName(brGatewayIface.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to lookup %q: %v", brGatewayIface.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return brGatewayVeth, nil
|
||||
}
|
||||
|
||||
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int) (*current.Interface, *current.Interface, error) {
|
||||
contIface := ¤t.Interface{}
|
||||
hostIface := ¤t.Interface{}
|
||||
|
||||
@ -284,6 +315,13 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
|
||||
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
if vlanID != 0 {
|
||||
err = netlink.BridgeVlanAdd(hostVeth, uint16(vlanID), true, true, false, true)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to setup vlan tag on interface %q: %v", hostIface.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return hostIface, contIface, nil
|
||||
}
|
||||
|
||||
@ -293,8 +331,12 @@ func calcGatewayIP(ipn *net.IPNet) net.IP {
|
||||
}
|
||||
|
||||
func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
|
||||
vlanFiltering := false
|
||||
if n.Vlan != 0 {
|
||||
vlanFiltering = true
|
||||
}
|
||||
// create bridge if necessary
|
||||
br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode)
|
||||
br, err := ensureBridge(n.BrName, n.MTU, n.PromiscMode, vlanFiltering)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create bridge %q: %v", n.BrName, err)
|
||||
}
|
||||
@ -355,7 +397,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)
|
||||
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -435,16 +477,34 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
if n.IsGW {
|
||||
var firstV4Addr net.IP
|
||||
var vlanInterface *current.Interface
|
||||
// Set the IP address(es) on the bridge and enable forwarding
|
||||
for _, gws := range []*gwInfo{gwsV4, gwsV6} {
|
||||
for _, gw := range gws.gws {
|
||||
if gw.IP.To4() != nil && firstV4Addr == nil {
|
||||
firstV4Addr = gw.IP
|
||||
}
|
||||
if n.Vlan != 0 {
|
||||
vlanIface, err := ensureVlanInterface(br, n.Vlan)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create vlan interface: %v", err)
|
||||
}
|
||||
|
||||
err = ensureBridgeAddr(br, gws.family, &gw, n.ForceAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set bridge addr: %v", err)
|
||||
if vlanInterface == nil {
|
||||
vlanInterface = ¤t.Interface{Name: vlanIface.Attrs().Name,
|
||||
Mac: vlanIface.Attrs().HardwareAddr.String()}
|
||||
result.Interfaces = append(result.Interfaces, vlanInterface)
|
||||
}
|
||||
|
||||
err = ensureAddr(vlanIface, gws.family, &gw, n.ForceAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set vlan interface for bridge with addr: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = ensureAddr(br, gws.family, &gw, n.ForceAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set bridge addr: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,11 +596,269 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
type cniBridgeIf struct {
|
||||
Name string
|
||||
ifIndex int
|
||||
peerIndex int
|
||||
masterIndex int
|
||||
found bool
|
||||
}
|
||||
|
||||
func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, netlink.Link, error) {
|
||||
|
||||
ifFound := cniBridgeIf{found: false}
|
||||
if intf.Name == "" {
|
||||
return ifFound, nil, fmt.Errorf("Interface name missing ")
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return ifFound, nil, fmt.Errorf("Interface name %s not found", intf.Name)
|
||||
}
|
||||
|
||||
if expectInSb {
|
||||
if intf.Sandbox == "" {
|
||||
return ifFound, nil, fmt.Errorf("Interface %s is expected to be in a sandbox", intf.Name)
|
||||
}
|
||||
} else {
|
||||
if intf.Sandbox != "" {
|
||||
return ifFound, nil, fmt.Errorf("Interface %s should not be in sandbox", intf.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return ifFound, link, err
|
||||
}
|
||||
|
||||
func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, error) {
|
||||
|
||||
brFound, link, err := validateInterface(intf, false)
|
||||
if err != nil {
|
||||
return brFound, err
|
||||
}
|
||||
|
||||
_, isBridge := link.(*netlink.Bridge)
|
||||
if !isBridge {
|
||||
return brFound, fmt.Errorf("Interface %s does not have link type of bridge", intf.Name)
|
||||
}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return brFound, fmt.Errorf("Bridge interface %s Mac doesn't match: %s", intf.Name, intf.Mac)
|
||||
}
|
||||
}
|
||||
|
||||
linkPromisc := link.Attrs().Promisc != 0
|
||||
if linkPromisc != n.PromiscMode {
|
||||
return brFound, fmt.Errorf("Bridge interface %s configured Promisc Mode %v doesn't match current state: %v ",
|
||||
intf.Name, n.PromiscMode, linkPromisc)
|
||||
}
|
||||
|
||||
brFound.found = true
|
||||
brFound.Name = link.Attrs().Name
|
||||
brFound.ifIndex = link.Attrs().Index
|
||||
brFound.masterIndex = link.Attrs().MasterIndex
|
||||
|
||||
return brFound, nil
|
||||
}
|
||||
|
||||
func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf cniBridgeIf) (cniBridgeIf, error) {
|
||||
|
||||
vethFound, link, err := validateInterface(*intf, false)
|
||||
if err != nil {
|
||||
return vethFound, err
|
||||
}
|
||||
|
||||
_, isVeth := link.(*netlink.Veth)
|
||||
if !isVeth {
|
||||
// just skip it, it's not what CNI created
|
||||
return vethFound, nil
|
||||
}
|
||||
|
||||
_, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name)
|
||||
if err != nil {
|
||||
return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name)
|
||||
}
|
||||
vethFound.ifIndex = link.Attrs().Index
|
||||
vethFound.masterIndex = link.Attrs().MasterIndex
|
||||
|
||||
if vethFound.ifIndex != contIf.peerIndex {
|
||||
return vethFound, nil
|
||||
}
|
||||
|
||||
if contIf.ifIndex != vethFound.peerIndex {
|
||||
return vethFound, nil
|
||||
}
|
||||
|
||||
if vethFound.masterIndex != brIf.ifIndex {
|
||||
return vethFound, nil
|
||||
}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return vethFound, fmt.Errorf("Interface %s Mac doesn't match: %s not found", intf.Name, intf.Mac)
|
||||
}
|
||||
}
|
||||
|
||||
vethFound.found = true
|
||||
vethFound.Name = link.Attrs().Name
|
||||
|
||||
return vethFound, nil
|
||||
}
|
||||
|
||||
func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error) {
|
||||
|
||||
vethFound, link, err := validateInterface(intf, true)
|
||||
if err != nil {
|
||||
return vethFound, err
|
||||
}
|
||||
|
||||
_, isVeth := link.(*netlink.Veth)
|
||||
if !isVeth {
|
||||
return vethFound, fmt.Errorf("Error: Container interface %s not of type veth", link.Attrs().Name)
|
||||
}
|
||||
_, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name)
|
||||
if err != nil {
|
||||
return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name)
|
||||
}
|
||||
vethFound.ifIndex = link.Attrs().Index
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return vethFound, fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
vethFound.found = true
|
||||
vethFound.Name = link.Attrs().Name
|
||||
|
||||
return vethFound, nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
n, _, err := loadNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if n.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(&n.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := current.NewResultFromResult(n.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errLink error
|
||||
var contCNI, vethCNI cniBridgeIf
|
||||
var brMap, contMap current.Interface
|
||||
|
||||
// Find interfaces for names whe know, CNI Bridge and container
|
||||
for _, intf := range result.Interfaces {
|
||||
if n.BrName == intf.Name {
|
||||
brMap = *intf
|
||||
continue
|
||||
} else if args.IfName == intf.Name {
|
||||
if args.Netns == intf.Sandbox {
|
||||
contMap = *intf
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
brCNI, err := validateCniBrInterface(brMap, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The namespace must be the same as what was configured
|
||||
if args.Netns != contMap.Sandbox {
|
||||
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
|
||||
contMap.Sandbox, args.Netns)
|
||||
}
|
||||
|
||||
// Check interface against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
contCNI, errLink = validateCniContainerInterface(contMap)
|
||||
if errLink != nil {
|
||||
return errLink
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now look for veth that is peer with container interface.
|
||||
// Anything else wasn't created by CNI, skip it
|
||||
for _, intf := range result.Interfaces {
|
||||
// Skip this result if name is the same as cni bridge
|
||||
// It's either the cni bridge we dealt with above, or something with the
|
||||
// same name in a different namespace. We just skip since it's not ours
|
||||
if brMap.Name == intf.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// same here for container name
|
||||
if contMap.Name == intf.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
vethCNI, errLink = validateCniVethInterface(intf, brCNI, contCNI)
|
||||
if errLink != nil {
|
||||
return errLink
|
||||
}
|
||||
|
||||
if vethCNI.found {
|
||||
// veth with container interface as peer and bridge as master found
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !brCNI.found {
|
||||
return fmt.Errorf("CNI created bridge %s in host namespace was not found", n.BrName)
|
||||
}
|
||||
if !contCNI.found {
|
||||
return fmt.Errorf("CNI created interface in container %s not found", args.IfName)
|
||||
}
|
||||
if !vethCNI.found {
|
||||
return fmt.Errorf("CNI veth created for bridge %s was not found", n.BrName)
|
||||
}
|
||||
|
||||
// Check prevResults for ips, routes and dns against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedRoute(result.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,7 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
@ -30,15 +32,33 @@ import (
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
BRNAME = "bridge0"
|
||||
IFNAME = "eth0"
|
||||
BRNAME = "bridge0"
|
||||
BRNAMEVLAN = "bridge0.100"
|
||||
IFNAME = "eth0"
|
||||
)
|
||||
|
||||
type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
BrName string `json:"bridge"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
//RuntimeConfig struct { // The capability arg
|
||||
// IPRanges []RangeSet `json:"ipRanges,omitempty"`
|
||||
//} `json:"runtimeConfig,omitempty"`
|
||||
//Args *struct {
|
||||
// A *IPAMArgs `json:"cni"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult current.Result `json:"-"`
|
||||
}
|
||||
|
||||
// testCase defines the CNI network configuration and the expected
|
||||
// bridge addresses for a test case.
|
||||
type testCase struct {
|
||||
@ -49,6 +69,7 @@ type testCase struct {
|
||||
isGW bool
|
||||
isLayer2 bool
|
||||
expGWCIDRs []string // Expected gateway addresses in CIDR form
|
||||
vlan int
|
||||
}
|
||||
|
||||
// Range definition for each entry in the ranges list
|
||||
@ -78,9 +99,12 @@ const (
|
||||
"cniVersion": "%s",
|
||||
"name": "testConfig",
|
||||
"type": "bridge",
|
||||
"bridge": "%s",`
|
||||
"bridge": "%s"`
|
||||
|
||||
netDefault = `
|
||||
vlan = `,
|
||||
"vlan": %d`
|
||||
|
||||
netDefault = `,
|
||||
"isDefaultGateway": true,
|
||||
"ipMasq": false`
|
||||
|
||||
@ -120,6 +144,10 @@ const (
|
||||
// for a test case.
|
||||
func (tc testCase) netConfJSON(dataDir string) string {
|
||||
conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME)
|
||||
if tc.vlan != 0 {
|
||||
conf += fmt.Sprintf(vlan, tc.vlan)
|
||||
}
|
||||
|
||||
if !tc.isLayer2 {
|
||||
conf += netDefault
|
||||
if tc.subnet != "" || tc.ranges != nil {
|
||||
@ -136,7 +164,7 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
conf += ipamEndStr
|
||||
}
|
||||
} else {
|
||||
conf += `
|
||||
conf += `,
|
||||
"ipam": {}`
|
||||
}
|
||||
return "{" + conf + "\n}"
|
||||
@ -171,7 +199,24 @@ var counter uint
|
||||
// arguments for a test case.
|
||||
func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs {
|
||||
conf := tc.netConfJSON(dataDir)
|
||||
defer func() { counter += 1 }()
|
||||
//defer func() { counter += 1 }()
|
||||
return &skel.CmdArgs{
|
||||
ContainerID: fmt.Sprintf("dummy-%d", counter),
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
}
|
||||
|
||||
// createCheckCmdArgs generates network configuration and creates command
|
||||
// arguments for a Check test case.
|
||||
func (tc testCase) createCheckCmdArgs(targetNS ns.NetNS, config *Net, dataDir string) *skel.CmdArgs {
|
||||
|
||||
conf, err := json.Marshal(config)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// TODO Don't we need to use the same counter as before?
|
||||
//defer func() { counter += 1 }()
|
||||
return &skel.CmdArgs{
|
||||
ContainerID: fmt.Sprintf("dummy-%d", counter),
|
||||
Netns: targetNS.Path(),
|
||||
@ -218,6 +263,38 @@ func delBridgeAddrs(testNS ns.NetNS) {
|
||||
}
|
||||
}
|
||||
|
||||
br, err = netlink.LinkByName(BRNAMEVLAN)
|
||||
if err == nil {
|
||||
addrs, err = netlink.AddrList(br, netlink.FAMILY_ALL)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
for _, addr := range addrs {
|
||||
if !addr.IP.IsLinkLocalUnicast() {
|
||||
err = netlink.AddrDel(br, &addr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func delVlanAddrs(testNS ns.NetNS, vlan int) {
|
||||
err := testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
vlanLink, err := netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, vlan))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
addrs, err := netlink.AddrList(vlanLink, netlink.FAMILY_ALL)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
for _, addr := range addrs {
|
||||
if !addr.IP.IsLinkLocalUnicast() {
|
||||
err = netlink.AddrDel(vlanLink, &addr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -248,14 +325,27 @@ func countIPAMIPs(path string) (int, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func checkVlan(vlanId int, bridgeVlanInfo []*nl.BridgeVlanInfo) bool {
|
||||
for _, vlan := range bridgeVlanInfo {
|
||||
if vlan.Vid == uint16(vlanId) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type cmdAddDelTester interface {
|
||||
setNS(testNS ns.NetNS, targetNS ns.NetNS)
|
||||
cmdAddTest(tc testCase, dataDir string)
|
||||
cmdDelTest(tc testCase)
|
||||
cmdAddTest(tc testCase, dataDir string) (*current.Result, error)
|
||||
cmdCheckTest(tc testCase, conf *Net, dataDir string)
|
||||
cmdDelTest(tc testCase, dataDir string)
|
||||
}
|
||||
|
||||
func testerByVersion(version string) cmdAddDelTester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
case strings.HasPrefix(version, "0.3."):
|
||||
return &testerV03x{}
|
||||
default:
|
||||
@ -263,19 +353,19 @@ func testerByVersion(version string) cmdAddDelTester {
|
||||
}
|
||||
}
|
||||
|
||||
type testerV03x struct {
|
||||
type testerV04x struct {
|
||||
testNS ns.NetNS
|
||||
targetNS ns.NetNS
|
||||
args *skel.CmdArgs
|
||||
vethName string
|
||||
}
|
||||
|
||||
func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
|
||||
func (tester *testerV04x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
|
||||
tester.testNS = testNS
|
||||
tester.targetNS = targetNS
|
||||
}
|
||||
|
||||
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
|
||||
func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
|
||||
// Generate network config and command arguments
|
||||
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
||||
|
||||
@ -345,10 +435,83 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
|
||||
// Check that the bridge has a different mac from the veth
|
||||
// If not, it means the bridge has an unstable mac and will change
|
||||
// as ifs are added and removed
|
||||
// this check is not relevant for a layer 2 bridge
|
||||
if !tc.isLayer2 {
|
||||
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
|
||||
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Find the veth peer in the container namespace and the default route
|
||||
err = tester.targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
|
||||
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Ignore link local address which may or may not be
|
||||
// ready when we read addresses.
|
||||
var foundAddrs int
|
||||
for _, addr := range addrs {
|
||||
if !addr.IP.IsLinkLocalUnicast() {
|
||||
foundAddrs++
|
||||
}
|
||||
}
|
||||
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
|
||||
|
||||
// Ensure the default route(s)
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var defaultRouteFound4, defaultRouteFound6 bool
|
||||
for _, cidr := range tc.expGWCIDRs {
|
||||
gwIP, _, err := net.ParseCIDR(cidr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var found *bool
|
||||
if ipVersion(gwIP) == "4" {
|
||||
found = &defaultRouteFound4
|
||||
} else {
|
||||
found = &defaultRouteFound6
|
||||
}
|
||||
if *found == true {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(*found).To(Equal(true))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
|
||||
// Generate network config and command arguments
|
||||
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf, dataDir)
|
||||
|
||||
// Execute cmdCHECK on the plugin
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdCheckWithArgs(tester.args, func() error {
|
||||
return cmdCheck(tester.args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -411,7 +574,244 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func (tester *testerV03x) cmdDelTest(tc testCase) {
|
||||
func (tester *testerV04x) cmdDelTest(tc testCase, dataDir string) {
|
||||
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdDelWithArgs(tester.args, func() error {
|
||||
return cmdDel(tester.args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure the host veth has been deleted
|
||||
err = tester.targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure the container veth has been deleted
|
||||
err = tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(tester.vethName)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
type testerV03x struct {
|
||||
testNS ns.NetNS
|
||||
targetNS ns.NetNS
|
||||
args *skel.CmdArgs
|
||||
vethName string
|
||||
}
|
||||
|
||||
func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
|
||||
tester.testNS = testNS
|
||||
tester.targetNS = targetNS
|
||||
}
|
||||
|
||||
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
|
||||
// Generate network config and command arguments
|
||||
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
||||
|
||||
// Execute cmdADD on the plugin
|
||||
var result *current.Result
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
|
||||
return cmdAdd(tester.args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
if !tc.isLayer2 && tc.vlan != 0 {
|
||||
Expect(len(result.Interfaces)).To(Equal(4))
|
||||
} else {
|
||||
Expect(len(result.Interfaces)).To(Equal(3))
|
||||
}
|
||||
|
||||
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
|
||||
Expect(result.Interfaces[0].Mac).To(HaveLen(17))
|
||||
|
||||
Expect(result.Interfaces[1].Name).To(HavePrefix("veth"))
|
||||
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
|
||||
|
||||
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
|
||||
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
|
||||
Expect(result.Interfaces[2].Sandbox).To(Equal(tester.targetNS.Path()))
|
||||
|
||||
// Make sure bridge link exists
|
||||
link, err := netlink.LinkByName(result.Interfaces[0].Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
|
||||
bridgeMAC := link.Attrs().HardwareAddr.String()
|
||||
|
||||
var vlanLink netlink.Link
|
||||
if !tc.isLayer2 && tc.vlan != 0 {
|
||||
// Make sure vlan link exists
|
||||
vlanLink, err = netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, tc.vlan))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(vlanLink.Attrs().Name).To(Equal(fmt.Sprintf("%s.%d", BRNAME, tc.vlan)))
|
||||
Expect(vlanLink).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
|
||||
// Check the bridge dot vlan interface have the vlan tag
|
||||
peerLink, err := netlink.LinkByIndex(vlanLink.Attrs().Index - 1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
interfaceMap, err := netlink.BridgeVlanList()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
vlans, isExist := interfaceMap[int32(peerLink.Attrs().Index)]
|
||||
Expect(isExist).To(BeTrue())
|
||||
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
|
||||
}
|
||||
|
||||
// Check the bridge vlan filtering equals true
|
||||
if tc.vlan != 0 {
|
||||
Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(true))
|
||||
} else {
|
||||
Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(false))
|
||||
}
|
||||
|
||||
// Ensure bridge has expected gateway address(es)
|
||||
var addrs []netlink.Addr
|
||||
if tc.vlan == 0 {
|
||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||
} else {
|
||||
addrs, err = netlink.AddrList(vlanLink, netlink.FAMILY_ALL)
|
||||
}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(BeNumerically(">", 0))
|
||||
for _, cidr := range tc.expGWCIDRs {
|
||||
ip, subnet, err := net.ParseCIDR(cidr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
found := false
|
||||
subnetPrefix, subnetBits := subnet.Mask.Size()
|
||||
for _, a := range addrs {
|
||||
aPrefix, aBits := a.IPNet.Mask.Size()
|
||||
if a.IPNet.IP.Equal(ip) && aPrefix == subnetPrefix && aBits == subnetBits {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(found).To(Equal(true))
|
||||
}
|
||||
|
||||
// Check for the veth link in the main namespace
|
||||
links, err := netlink.LinkList()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if !tc.isLayer2 && tc.vlan != 0 {
|
||||
Expect(len(links)).To(Equal(5)) // Bridge, Bridge vlan veth, veth, and loopback
|
||||
} else {
|
||||
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
|
||||
}
|
||||
|
||||
link, err = netlink.LinkByName(result.Interfaces[1].Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
tester.vethName = result.Interfaces[1].Name
|
||||
|
||||
// check vlan exist on the veth interface
|
||||
if tc.vlan != 0 {
|
||||
interfaceMap, err := netlink.BridgeVlanList()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
|
||||
Expect(isExist).To(BeTrue())
|
||||
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
|
||||
}
|
||||
|
||||
// Check that the bridge has a different mac from the veth
|
||||
// If not, it means the bridge has an unstable mac and will change
|
||||
// as ifs are added and removed
|
||||
// this check is not relevant for a layer 2 bridge
|
||||
if !tc.isLayer2 && tc.vlan == 0 {
|
||||
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Find the veth peer in the container namespace and the default route
|
||||
err = tester.targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
|
||||
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Ignore link local address which may or may not be
|
||||
// ready when we read addresses.
|
||||
var foundAddrs int
|
||||
for _, addr := range addrs {
|
||||
if !addr.IP.IsLinkLocalUnicast() {
|
||||
foundAddrs++
|
||||
}
|
||||
}
|
||||
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
|
||||
|
||||
// Ensure the default route(s)
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var defaultRouteFound4, defaultRouteFound6 bool
|
||||
for _, cidr := range tc.expGWCIDRs {
|
||||
gwIP, _, err := net.ParseCIDR(cidr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var found *bool
|
||||
if ipVersion(gwIP) == "4" {
|
||||
found = &defaultRouteFound4
|
||||
} else {
|
||||
found = &defaultRouteFound6
|
||||
}
|
||||
if *found == true {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(*found).To(Equal(true))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (tester *testerV03x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (tester *testerV03x) cmdDelTest(tc testCase, dataDir string) {
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -458,7 +858,7 @@ func (tester *testerV01xOr02x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
|
||||
tester.targetNS = targetNS
|
||||
}
|
||||
|
||||
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) {
|
||||
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
|
||||
// Generate network config and calculate gateway addresses
|
||||
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
||||
|
||||
@ -549,9 +949,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) {
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (tester *testerV01xOr02x) cmdDelTest(tc testCase) {
|
||||
func (tester *testerV01xOr02x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (tester *testerV01xOr02x) cmdDelTest(tc testCase, dataDir string) {
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -586,15 +991,107 @@ func cmdAddDelTest(testNS ns.NetNS, tc testCase, dataDir string) {
|
||||
tester.setNS(testNS, targetNS)
|
||||
|
||||
// Test IP allocation
|
||||
tester.cmdAddTest(tc, dataDir)
|
||||
result, err := tester.cmdAddTest(tc, dataDir)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
if strings.HasPrefix(tc.cniVersion, "0.3.") {
|
||||
Expect(result).NotTo(BeNil())
|
||||
} else {
|
||||
Expect(result).To(BeNil())
|
||||
}
|
||||
|
||||
// Test IP Release
|
||||
tester.cmdDelTest(tc)
|
||||
tester.cmdDelTest(tc, dataDir)
|
||||
|
||||
// Clean up bridge addresses for next test case
|
||||
delBridgeAddrs(testNS)
|
||||
}
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": name,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &Net{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
|
||||
}
|
||||
|
||||
func cmdAddDelCheckTest(testNS ns.NetNS, tc testCase, dataDir string) {
|
||||
Expect(tc.cniVersion).To(Equal("0.4.0"))
|
||||
|
||||
// Get a Add/Del tester based on test case version
|
||||
tester := testerByVersion(tc.cniVersion)
|
||||
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNS.Close()
|
||||
tester.setNS(testNS, targetNS)
|
||||
|
||||
// Test IP allocation
|
||||
prevResult, err := tester.cmdAddTest(tc, dataDir)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(prevResult).NotTo(BeNil())
|
||||
|
||||
confString := tc.netConfJSON(dataDir)
|
||||
|
||||
conf := &Net{}
|
||||
err = json.Unmarshal([]byte(confString), &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf.IPAM, _, err = allocator.LoadIPAMConfig([]byte(confString), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
newConf, err := buildOneConfig("testConfig", tc.cniVersion, conf, prevResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Test CHECK
|
||||
tester.cmdCheckTest(tc, newConf, dataDir)
|
||||
|
||||
// Test IP Release
|
||||
tester.cmdDelTest(tc, dataDir)
|
||||
|
||||
// Clean up bridge addresses for next test case
|
||||
delBridgeAddrs(testNS)
|
||||
|
||||
if tc.vlan != 0 && !tc.isLayer2 {
|
||||
delVlanAddrs(testNS, tc.vlan)
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("bridge Operations", func() {
|
||||
var originalNS ns.NetNS
|
||||
var dataDir string
|
||||
@ -722,6 +1219,63 @@ var _ = Describe("bridge Operations", func() {
|
||||
cmdAddDelTest(originalNS, tc, dataDir)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() {
|
||||
tc := testCase{cniVersion: "0.3.0", isLayer2: true, vlan: 100}
|
||||
cmdAddDelTest(originalNS, tc, dataDir)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() {
|
||||
tc := testCase{cniVersion: "0.3.1", isLayer2: true, vlan: 100}
|
||||
cmdAddDelTest(originalNS, tc, dataDir)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a bridge, veth with default route and vlanID 100 with ADD/DEL for 0.3.0 config", func() {
|
||||
testCases := []testCase{
|
||||
{
|
||||
// IPv4 only
|
||||
subnet: "10.1.2.0/24",
|
||||
expGWCIDRs: []string{"10.1.2.1/24"},
|
||||
vlan: 100,
|
||||
},
|
||||
{
|
||||
// IPv6 only
|
||||
subnet: "2001:db8::0/64",
|
||||
expGWCIDRs: []string{"2001:db8::1/64"},
|
||||
vlan: 100,
|
||||
},
|
||||
{
|
||||
// Dual-Stack
|
||||
ranges: []rangeInfo{
|
||||
{subnet: "192.168.0.0/24"},
|
||||
{subnet: "fd00::0/64"},
|
||||
},
|
||||
expGWCIDRs: []string{
|
||||
"192.168.0.1/24",
|
||||
"fd00::1/64",
|
||||
},
|
||||
vlan: 100,
|
||||
},
|
||||
{
|
||||
// 3 Subnets (1 IPv4 and 2 IPv6 subnets)
|
||||
ranges: []rangeInfo{
|
||||
{subnet: "192.168.0.0/24"},
|
||||
{subnet: "fd00::0/64"},
|
||||
{subnet: "2001:db8::0/64"},
|
||||
},
|
||||
expGWCIDRs: []string{
|
||||
"192.168.0.1/24",
|
||||
"fd00::1/64",
|
||||
"2001:db8::1/64",
|
||||
},
|
||||
vlan: 100,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc.cniVersion = "0.3.0"
|
||||
cmdAddDelTest(originalNS, tc, dataDir)
|
||||
}
|
||||
})
|
||||
|
||||
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() {
|
||||
testCases := []testCase{
|
||||
{
|
||||
@ -767,7 +1321,7 @@ var _ = Describe("bridge Operations", func() {
|
||||
tester.args = tc.createCmdArgs(targetNS, dataDir)
|
||||
|
||||
// Execute cmdDEL on the plugin, expect no errors
|
||||
tester.cmdDelTest(tc)
|
||||
tester.cmdDelTest(tc, dataDir)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() {
|
||||
@ -870,13 +1424,13 @@ var _ = Describe("bridge Operations", func() {
|
||||
Expect(conf.ForceAddress).To(Equal(false))
|
||||
|
||||
// Set first address on bridge
|
||||
err = ensureBridgeAddr(bridge, family, &gwnFirst, conf.ForceAddress)
|
||||
err = ensureAddr(bridge, family, &gwnFirst, conf.ForceAddress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
checkBridgeIPs(tc.gwCIDRFirst, "")
|
||||
|
||||
// Attempt to set the second address on the bridge
|
||||
// with ForceAddress set to false.
|
||||
err = ensureBridgeAddr(bridge, family, &gwnSecond, false)
|
||||
err = ensureAddr(bridge, family, &gwnSecond, false)
|
||||
if family == netlink.FAMILY_V4 || subnetsOverlap {
|
||||
// IPv4 or overlapping IPv6 subnets:
|
||||
// Expect an error, and address should remain the same
|
||||
@ -892,7 +1446,7 @@ var _ = Describe("bridge Operations", func() {
|
||||
|
||||
// Set the second address on the bridge
|
||||
// with ForceAddress set to true.
|
||||
err = ensureBridgeAddr(bridge, family, &gwnSecond, true)
|
||||
err = ensureAddr(bridge, family, &gwnSecond, true)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if family == netlink.FAMILY_V4 || subnetsOverlap {
|
||||
// IPv4 or overlapping IPv6 subnets:
|
||||
@ -1007,4 +1561,38 @@ var _ = Describe("bridge Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures a bridge and veth with default route with ADD/DEL/CHECK for 0.4.0 config", func() {
|
||||
testCases := []testCase{
|
||||
{
|
||||
// IPv4 only
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
expGWCIDRs: []string{"10.1.2.1/24"},
|
||||
},
|
||||
{
|
||||
// IPv6 only
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "2001:db8::0/64",
|
||||
}},
|
||||
expGWCIDRs: []string{"2001:db8::1/64"},
|
||||
},
|
||||
{
|
||||
// Dual-Stack
|
||||
ranges: []rangeInfo{
|
||||
{subnet: "192.168.0.0/24"},
|
||||
{subnet: "fd00::0/64"},
|
||||
},
|
||||
expGWCIDRs: []string{
|
||||
"192.168.0.1/24",
|
||||
"fd00::1/64",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc.cniVersion = "0.4.0"
|
||||
cmdAddDelCheckTest(originalNS, tc, dataDir)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -25,13 +25,17 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
//NetConf for host-device config, look the README to learn how to use those parameters
|
||||
@ -81,6 +85,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("failed to move link %v", err)
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
if cfg.IPAM.Type != "" {
|
||||
r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData)
|
||||
@ -96,7 +101,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}()
|
||||
|
||||
// Convert whatever the IPAM result was into the current Result type
|
||||
result, err := current.NewResultFromResult(r)
|
||||
result, err = current.NewResultFromResult(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -124,6 +129,10 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.DNS = cfg.DNS
|
||||
|
||||
return types.PrintResult(result, cfg.CNIVersion)
|
||||
}
|
||||
|
||||
return printLink(contDev, cfg.CNIVersion, containerNs)
|
||||
@ -275,11 +284,109 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
cfg, err := loadConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
if cfg.IPAM.Type != "" {
|
||||
err = ipam.ExecCheck(cfg.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if cfg.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(&cfg.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := current.NewResultFromResult(cfg.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contMap current.Interface
|
||||
// Find interfaces for name we know, that of host-device inside container
|
||||
for _, intf := range result.Interfaces {
|
||||
if args.IfName == intf.Name {
|
||||
if args.Netns == intf.Sandbox {
|
||||
contMap = *intf
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The namespace must be the same as what was configured
|
||||
if args.Netns != contMap.Sandbox {
|
||||
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
|
||||
contMap.Sandbox, args.Netns)
|
||||
}
|
||||
|
||||
//
|
||||
// Check prevResults for ips, routes and dns against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
|
||||
// Check interface against values found in the container
|
||||
err := validateCniContainerInterface(contMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedRoute(result.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
var link netlink.Link
|
||||
var err error
|
||||
|
||||
if intf.Name == "" {
|
||||
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
|
||||
}
|
||||
link, err = netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
|
||||
}
|
||||
if intf.Sandbox == "" {
|
||||
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,19 +15,210 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||
IPAM *IPAMConfig `json:"ipam,omitempty"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult current.Result `json:"-"`
|
||||
}
|
||||
|
||||
type IPAMConfig struct {
|
||||
Name string
|
||||
Type string `json:"type"`
|
||||
Routes []*types.Route `json:"routes"`
|
||||
Addresses []Address `json:"addresses,omitempty"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
}
|
||||
|
||||
type IPAMEnvArgs struct {
|
||||
types.CommonArgs
|
||||
IP types.UnmarshallableString `json:"ip,omitempty"`
|
||||
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
AddressStr string `json:"address"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
Address net.IPNet
|
||||
Version string
|
||||
}
|
||||
|
||||
// canonicalizeIP makes sure a provided ip is in standard form
|
||||
func canonicalizeIP(ip *net.IP) error {
|
||||
if ip.To4() != nil {
|
||||
*ip = ip.To4()
|
||||
return nil
|
||||
} else if ip.To16() != nil {
|
||||
*ip = ip.To16()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("IP %s not v4 nor v6", *ip)
|
||||
}
|
||||
|
||||
// LoadIPAMConfig creates IPAMConfig using json encoded configuration provided
|
||||
// as `bytes`. At the moment values provided in envArgs are ignored so there
|
||||
// is no possibility to overload the json configuration using envArgs
|
||||
func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
n := Net{}
|
||||
if err := json.Unmarshal(bytes, &n); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if n.IPAM == nil {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
|
||||
// Validate all ranges
|
||||
numV4 := 0
|
||||
numV6 := 0
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err)
|
||||
}
|
||||
n.IPAM.Addresses[i].Address = *addr
|
||||
n.IPAM.Addresses[i].Address.IP = ip
|
||||
|
||||
if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil {
|
||||
return nil, "", fmt.Errorf("invalid address %d: %s", i, err)
|
||||
}
|
||||
|
||||
if n.IPAM.Addresses[i].Address.IP.To4() != nil {
|
||||
n.IPAM.Addresses[i].Version = "4"
|
||||
numV4++
|
||||
} else {
|
||||
n.IPAM.Addresses[i].Version = "6"
|
||||
numV6++
|
||||
}
|
||||
}
|
||||
|
||||
if envArgs != "" {
|
||||
e := IPAMEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.IP != "" {
|
||||
for _, item := range strings.Split(string(e.IP), ",") {
|
||||
ipstr := strings.TrimSpace(item)
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(ipstr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
|
||||
}
|
||||
|
||||
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
|
||||
if addr.Address.IP.To4() != nil {
|
||||
addr.Version = "4"
|
||||
numV4++
|
||||
} else {
|
||||
addr.Version = "6"
|
||||
numV6++
|
||||
}
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if e.GATEWAY != "" {
|
||||
for _, item := range strings.Split(string(e.GATEWAY), ",") {
|
||||
gwip := net.ParseIP(strings.TrimSpace(item))
|
||||
if gwip == nil {
|
||||
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
|
||||
}
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
if n.IPAM.Addresses[i].Address.Contains(gwip) {
|
||||
n.IPAM.Addresses[i].Gateway = gwip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CNI spec 0.2.0 and below supported only one v4 and v6 address
|
||||
if numV4 > 1 || numV6 > 1 {
|
||||
for _, v := range types020.SupportedVersions {
|
||||
if n.CNIVersion == v {
|
||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy net name into IPAM so not to drag Net struct around
|
||||
n.IPAM.Name = n.Name
|
||||
|
||||
return n.IPAM, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": name,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &Net{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("base functionality", func() {
|
||||
var originalNS ns.NetNS
|
||||
var ifname string
|
||||
@ -262,4 +453,255 @@ var _ = Describe("base functionality", func() {
|
||||
|
||||
})
|
||||
|
||||
It("Works with a valid 0.4.0 config without IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniName := "eth0"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ifname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result was sane
|
||||
res, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||
{
|
||||
Name: cniName,
|
||||
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||
Sandbox: targetNS.Path(),
|
||||
},
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
newConf, err := buildOneConfig("testConfig", cniVersion, n, res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := json.Marshal(newConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check host-device in the target namespace
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Works with a valid 0.4.0 config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
targetIP := "10.10.0.1/24"
|
||||
cniName := "eth0"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [
|
||||
{
|
||||
"address":"`+targetIP+`",
|
||||
"gateway": "10.10.0.254"
|
||||
}]
|
||||
},
|
||||
"device": %q
|
||||
}`, ifname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result was sane
|
||||
res, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||
{
|
||||
Name: cniName,
|
||||
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||
Sandbox: targetNS.Path(),
|
||||
},
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
|
||||
//get the IP address of the interface in the target namespace
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
addr := addrs[0].IPNet.String()
|
||||
//assert that IP address is what we set
|
||||
Expect(addr).To(Equal(targetIP))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
newConf, err := buildOneConfig("testConfig", cniVersion, n, res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := json.Marshal(newConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check host-device in the target namespace
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -20,24 +20,21 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
|
||||
// support chaining for master interface and IP decisions
|
||||
// occurring prior to running ipvlan plugin
|
||||
RawPrevResult *map[string]interface{} `json:"prevResult"`
|
||||
PrevResult *current.Result `json:"-"`
|
||||
|
||||
Master string `json:"master"`
|
||||
Mode string `json:"mode"`
|
||||
MTU int `json:"mtu"`
|
||||
@ -50,33 +47,35 @@ func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) {
|
||||
n := &NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
if cmdCheck {
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
var err error
|
||||
// Parse previous result
|
||||
if n.RawPrevResult != nil {
|
||||
resultBytes, err := json.Marshal(n.RawPrevResult)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("could not serialize prevResult: %v", err)
|
||||
}
|
||||
res, err := version.NewResult(n.CNIVersion, resultBytes)
|
||||
if err != nil {
|
||||
if n.NetConf.RawPrevResult != nil {
|
||||
if err = version.ParsePrevResult(&n.NetConf); err != nil {
|
||||
return nil, "", fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
n.RawPrevResult = nil
|
||||
n.PrevResult, err = current.NewResultFromResult(res)
|
||||
|
||||
result, err = current.NewResultFromResult(n.PrevResult)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
if n.Master == "" {
|
||||
if n.PrevResult == nil {
|
||||
if result == nil {
|
||||
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
|
||||
}
|
||||
if len(n.PrevResult.Interfaces) == 1 && n.PrevResult.Interfaces[0].Name != "" {
|
||||
n.Master = n.PrevResult.Interfaces[0].Name
|
||||
if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
|
||||
n.Master = result.Interfaces[0].Name
|
||||
} else {
|
||||
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
|
||||
}
|
||||
@ -97,6 +96,19 @@ func modeFromString(s string) (netlink.IPVlanMode, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func modeToString(mode netlink.IPVlanMode) (string, error) {
|
||||
switch mode {
|
||||
case netlink.IPVLAN_MODE_L2:
|
||||
return "l2", nil
|
||||
case netlink.IPVLAN_MODE_L3:
|
||||
return "l3", nil
|
||||
case netlink.IPVLAN_MODE_L3S:
|
||||
return "l3s", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown ipvlan mode: %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
ipvlan := ¤t.Interface{}
|
||||
|
||||
@ -156,7 +168,7 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
n, cniVersion, err := loadConf(args.StdinData)
|
||||
n, cniVersion, err := loadConf(args.StdinData, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -175,9 +187,17 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
var result *current.Result
|
||||
// Configure iface from PrevResult if we have IPs and an IPAM
|
||||
// block has not been configured
|
||||
if n.IPAM.Type == "" && n.PrevResult != nil && len(n.PrevResult.IPs) > 0 {
|
||||
result = n.PrevResult
|
||||
} else {
|
||||
haveResult := false
|
||||
if n.IPAM.Type == "" && n.PrevResult != nil {
|
||||
result, err = current.NewResultFromResult(n.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(result.IPs) > 0 {
|
||||
haveResult = true
|
||||
}
|
||||
}
|
||||
if !haveResult {
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
@ -213,7 +233,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
n, _, err := loadConf(args.StdinData)
|
||||
n, _, err := loadConf(args.StdinData, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -245,11 +265,130 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ipvlan"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
n, _, err := loadConf(args.StdinData, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
if n.IPAM.Type != "" {
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if n.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(&n.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := current.NewResultFromResult(n.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contMap current.Interface
|
||||
// Find interfaces for names whe know, ipvlan inside container
|
||||
for _, intf := range result.Interfaces {
|
||||
if args.IfName == intf.Name {
|
||||
if args.Netns == intf.Sandbox {
|
||||
contMap = *intf
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The namespace must be the same as what was configured
|
||||
if args.Netns != contMap.Sandbox {
|
||||
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
|
||||
contMap.Sandbox, args.Netns)
|
||||
}
|
||||
|
||||
m, err := netlink.LinkByName(n.Master)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
|
||||
}
|
||||
|
||||
// Check prevResults for ips, routes and dns against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
|
||||
// Check interface against values found in the container
|
||||
err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedRoute(result.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCniContainerInterface(intf current.Interface, masterIndex int, modeExpected string) error {
|
||||
|
||||
var link netlink.Link
|
||||
var err error
|
||||
|
||||
if intf.Name == "" {
|
||||
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
|
||||
}
|
||||
link, err = netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
|
||||
}
|
||||
if intf.Sandbox == "" {
|
||||
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
}
|
||||
|
||||
ipv, isIPVlan := link.(*netlink.IPVlan)
|
||||
if !isIPVlan {
|
||||
return fmt.Errorf("Error: Container interface %s not of type ipvlan", link.Attrs().Name)
|
||||
}
|
||||
|
||||
mode, err := modeFromString(modeExpected)
|
||||
if ipv.Mode != mode {
|
||||
currString, err := modeToString(ipv.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confString, err := modeToString(mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("Container IPVlan mode %s does not match expected value: %s", currString, confString)
|
||||
}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
@ -27,12 +28,71 @@ import (
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const MASTER_NAME = "eth0"
|
||||
|
||||
type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Master string `json:"master"`
|
||||
Mode string `json:"mode"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult current.Result `json:"-"`
|
||||
}
|
||||
|
||||
func buildOneConfig(netName string, cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": netName,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
if orig.IPAM == nil {
|
||||
inject["master"] = master
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &Net{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
|
||||
}
|
||||
|
||||
func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -106,6 +166,109 @@ func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func ipvlanAddCheckDelTest(conf string, netName string, IFNAME string, originalNS ns.NetNS) {
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure ipvlan link exists in the target namespace
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
|
||||
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n := &Net{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
if n.IPAM != nil {
|
||||
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
newConf, err := buildOneConfig(netName, cniVersion, MASTER_NAME, n, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := json.Marshal(newConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check on macvlan in the target namespace
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure ipvlan link has been deleted
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
var _ = Describe("ipvlan Operations", func() {
|
||||
var originalNS ns.NetNS
|
||||
|
||||
@ -256,4 +419,49 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL", func() {
|
||||
const IFNAME = "ipvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "ipvlanTest1",
|
||||
"type": "ipvlan",
|
||||
"master": "%s",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
ipvlanAddCheckDelTest(conf, "ipvlanTest1", IFNAME, originalNS)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL when chained", func() {
|
||||
const IFNAME = "ipvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "ipvlanTest2",
|
||||
"type": "ipvlan",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.1.2.2/24",
|
||||
"gateway": "10.1.2.1",
|
||||
"interface": 0
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
ipvlanAddCheckDelTest(conf, "ipvlanTest2", IFNAME, originalNS)
|
||||
})
|
||||
})
|
||||
|
@ -15,13 +15,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
@ -73,11 +74,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("loopback"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
@ -21,16 +21,19 @@ import (
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -77,6 +80,21 @@ func modeFromString(s string) (netlink.MacvlanMode, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func modeToString(mode netlink.MacvlanMode) (string, error) {
|
||||
switch mode {
|
||||
case netlink.MACVLAN_MODE_BRIDGE:
|
||||
return "bridge", nil
|
||||
case netlink.MACVLAN_MODE_PRIVATE:
|
||||
return "private", nil
|
||||
case netlink.MACVLAN_MODE_VEPA:
|
||||
return "vepa", nil
|
||||
case netlink.MACVLAN_MODE_PASSTHRU:
|
||||
return "passthru", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown macvlan mode: %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
macvlan := ¤t.Interface{}
|
||||
|
||||
@ -255,11 +273,128 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("macvlan"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
n, _, err := loadConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if n.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(&n.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := current.NewResultFromResult(n.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contMap current.Interface
|
||||
// Find interfaces for names whe know, macvlan device name inside container
|
||||
for _, intf := range result.Interfaces {
|
||||
if args.IfName == intf.Name {
|
||||
if args.Netns == intf.Sandbox {
|
||||
contMap = *intf
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The namespace must be the same as what was configured
|
||||
if args.Netns != contMap.Sandbox {
|
||||
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
|
||||
contMap.Sandbox, args.Netns)
|
||||
}
|
||||
|
||||
m, err := netlink.LinkByName(n.Master)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
|
||||
}
|
||||
|
||||
// Check prevResults for ips, routes and dns against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
|
||||
// Check interface against values found in the container
|
||||
err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedRoute(result.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCniContainerInterface(intf current.Interface, parentIndex int, modeExpected string) error {
|
||||
|
||||
var link netlink.Link
|
||||
var err error
|
||||
|
||||
if intf.Name == "" {
|
||||
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
|
||||
}
|
||||
link, err = netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
|
||||
}
|
||||
if intf.Sandbox == "" {
|
||||
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
}
|
||||
|
||||
macv, isMacvlan := link.(*netlink.Macvlan)
|
||||
if !isMacvlan {
|
||||
return fmt.Errorf("Error: Container interface %s not of type macvlan", link.Attrs().Name)
|
||||
}
|
||||
|
||||
mode, err := modeFromString(modeExpected)
|
||||
if macv.Mode != mode {
|
||||
currString, err := modeToString(macv.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confString, err := modeToString(mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("Container macvlan mode %s does not match expected value: %s", currString, confString)
|
||||
}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
@ -27,12 +28,73 @@ import (
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const MASTER_NAME = "eth0"
|
||||
|
||||
type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Master string `json:"master"`
|
||||
Mode string `json:"mode"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
//RuntimeConfig struct { // The capability arg
|
||||
// IPRanges []RangeSet `json:"ipRanges,omitempty"`
|
||||
//} `json:"runtimeConfig,omitempty"`
|
||||
//Args *struct {
|
||||
// A *IPAMArgs `json:"cni"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult current.Result `json:"-"`
|
||||
}
|
||||
|
||||
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": netName,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &Net{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("macvlan Operations", func() {
|
||||
var originalNS ns.NetNS
|
||||
|
||||
@ -223,4 +285,118 @@ var _ = Describe("macvlan Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("configures and deconfigures a cniVersion 0.4.0 macvlan link with ADD/DEL", func() {
|
||||
const IFNAME = "macvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "macvlanTestv4",
|
||||
"type": "macvlan",
|
||||
"master": "%s",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"ranges": [[ {"subnet": "10.1.2.0/24", "gateway": "10.1.2.1"} ]]
|
||||
}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan link exists in the target namespace
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
|
||||
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n := &Net{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
newConf, err := buildOneConfig("macvlanTestv4", cniVersion, n, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := json.Marshal(newConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check on macvlan in the target namespace
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan link has been deleted
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
@ -22,16 +22,19 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -110,7 +113,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu
|
||||
}
|
||||
|
||||
for _, r := range []netlink.Route{
|
||||
netlink.Route{
|
||||
{
|
||||
LinkIndex: contVeth.Index,
|
||||
Dst: &net.IPNet{
|
||||
IP: ipc.Gateway,
|
||||
@ -119,7 +122,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
Src: ipc.Address.IP,
|
||||
},
|
||||
netlink.Route{
|
||||
{
|
||||
LinkIndex: contVeth.Index,
|
||||
Dst: &net.IPNet{
|
||||
IP: ipc.Address.IP.Mask(ipc.Address.Mask),
|
||||
@ -285,11 +288,108 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conf.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("ptp: Required prevResult missing")
|
||||
}
|
||||
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
// Convert whatever the IPAM result was into the current Result type
|
||||
result, err := current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contMap current.Interface
|
||||
// Find interfaces for name whe know, that of host-device inside container
|
||||
for _, intf := range result.Interfaces {
|
||||
if args.IfName == intf.Name {
|
||||
if args.Netns == intf.Sandbox {
|
||||
contMap = *intf
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The namespace must be the same as what was configured
|
||||
if args.Netns != contMap.Sandbox {
|
||||
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
|
||||
contMap.Sandbox, args.Netns)
|
||||
}
|
||||
|
||||
//
|
||||
// Check prevResults for ips, routes and dns against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
|
||||
// Check interface against values found in the container
|
||||
err := validateCniContainerInterface(contMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedRoute(result.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
var link netlink.Link
|
||||
var err error
|
||||
|
||||
if intf.Name == "" {
|
||||
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
|
||||
}
|
||||
link, err = netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
|
||||
}
|
||||
if intf.Sandbox == "" {
|
||||
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
}
|
||||
|
||||
_, isVeth := link.(*netlink.Veth)
|
||||
if !isVeth {
|
||||
return fmt.Errorf("Error: Container interface %s not of type veth/p2p", link.Attrs().Name)
|
||||
}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return fmt.Errorf("ptp: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
@ -25,10 +26,66 @@ import (
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
MTU int `json:"mtu"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult current.Result `json:"-"`
|
||||
}
|
||||
|
||||
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": netName,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &Net{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("ptp Operations", func() {
|
||||
var originalNS ns.NetNS
|
||||
|
||||
@ -142,6 +199,133 @@ var _ = Describe("ptp Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
doTestv4 := func(conf string, netName string, numIPs int) {
|
||||
const IFNAME = "ptp0"
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var resI types.Result
|
||||
var res *current.Result
|
||||
|
||||
// Execute the plugin with the ADD command, creating the veth endpoints
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
res, err = current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure ptp link exists in the target namespace
|
||||
// Then, ping the gateway
|
||||
seenIPs := 0
|
||||
|
||||
wantMac := ""
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
wantMac = link.Attrs().HardwareAddr.String()
|
||||
|
||||
for _, ipc := range res.IPs {
|
||||
if *ipc.Interface != 1 {
|
||||
continue
|
||||
}
|
||||
seenIPs += 1
|
||||
saddr := ipc.Address.IP.String()
|
||||
daddr := ipc.Gateway.String()
|
||||
fmt.Fprintln(GinkgoWriter, "ping", saddr, "->", daddr)
|
||||
|
||||
if err := testutils.Ping(saddr, daddr, (ipc.Version == "6"), 30); err != nil {
|
||||
return fmt.Errorf("ping %s -> %s failed: %s", saddr, daddr, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(seenIPs).To(Equal(numIPs))
|
||||
|
||||
// make sure the interfaces are correct
|
||||
Expect(res.Interfaces).To(HaveLen(2))
|
||||
|
||||
Expect(res.Interfaces[0].Name).To(HavePrefix("veth"))
|
||||
Expect(res.Interfaces[0].Mac).To(HaveLen(17))
|
||||
Expect(res.Interfaces[0].Sandbox).To(BeEmpty())
|
||||
|
||||
Expect(res.Interfaces[1].Name).To(Equal(IFNAME))
|
||||
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
|
||||
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
newConf, err := buildOneConfig(netName, cniVersion, n, res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := json.Marshal(newConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check host-device in the target namespace
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = []byte(conf)
|
||||
|
||||
// Call the plugins with the DEL command, deleting the veth endpoints
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure ptp link has been deleted
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
It("configures and deconfigures a ptp link with ADD/DEL", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
@ -215,4 +399,39 @@ var _ = Describe("ptp Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures a CNI V4 ptp link with ADD/DEL", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "ptpNetv4",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
|
||||
doTestv4(conf, "ptpNetv4", 1)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a CNI V4 dual-stack ptp link with ADD/DEL", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "ptpNetv4ds",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"ranges": [
|
||||
[{ "subnet": "10.1.2.0/24"}],
|
||||
[{ "subnet": "2001:db8:1::0/66"}]
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
doTestv4(conf, "ptpNetv4ds", 2)
|
||||
})
|
||||
})
|
||||
|
@ -20,14 +20,17 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
@ -192,11 +195,130 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("vlan"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conf.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("ptp: Required prevResult missing")
|
||||
}
|
||||
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
// Convert whatever the IPAM result was into the current Result type
|
||||
result, err := current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contMap current.Interface
|
||||
// Find interfaces for name whe know, that of host-device inside container
|
||||
for _, intf := range result.Interfaces {
|
||||
if args.IfName == intf.Name {
|
||||
if args.Netns == intf.Sandbox {
|
||||
contMap = *intf
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The namespace must be the same as what was configured
|
||||
if args.Netns != contMap.Sandbox {
|
||||
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
|
||||
contMap.Sandbox, args.Netns)
|
||||
}
|
||||
|
||||
m, err := netlink.LinkByName(conf.Master)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
|
||||
}
|
||||
|
||||
//
|
||||
// Check prevResults for ips, routes and dns against values found in the container
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
|
||||
// Check interface against values found in the container
|
||||
err := validateCniContainerInterface(contMap, m.Attrs().Index, conf.VlanId, conf.MTU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ip.ValidateExpectedRoute(result.Routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCniContainerInterface(intf current.Interface, masterIndex int, vlanId int, mtu int) error {
|
||||
|
||||
var link netlink.Link
|
||||
var err error
|
||||
|
||||
if intf.Name == "" {
|
||||
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
|
||||
}
|
||||
link, err = netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
|
||||
}
|
||||
if intf.Sandbox == "" {
|
||||
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
}
|
||||
|
||||
vlan, isVlan := link.(*netlink.Vlan)
|
||||
if !isVlan {
|
||||
return fmt.Errorf("Error: Container interface %s not of type vlan", link.Attrs().Name)
|
||||
}
|
||||
|
||||
// TODO This works when unit testing via cnitool; fails with ./test.sh
|
||||
//if masterIndex != vlan.Attrs().ParentIndex {
|
||||
// return fmt.Errorf("Container vlan Master %d does not match expected value: %d", vlan.Attrs().ParentIndex, masterIndex)
|
||||
//}
|
||||
|
||||
if intf.Mac != "" {
|
||||
if intf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return fmt.Errorf("vlan: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if vlanId != vlan.VlanId {
|
||||
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
|
||||
intf.Name, vlanId, vlan.VlanId)
|
||||
}
|
||||
|
||||
if mtu != 0 {
|
||||
if mtu != link.Attrs().MTU {
|
||||
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
|
||||
intf.Name, mtu, link.Attrs().MTU)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
@ -27,12 +28,69 @@ import (
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const MASTER_NAME = "eth0"
|
||||
|
||||
type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Master string `json:"master"`
|
||||
VlanId int `json:"vlanId"`
|
||||
MTU int `json:"mtu"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult current.Result `json:"-"`
|
||||
}
|
||||
|
||||
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": netName,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &Net{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("vlan Operations", func() {
|
||||
var originalNS ns.NetNS
|
||||
|
||||
@ -234,4 +292,119 @@ var _ = Describe("vlan Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() {
|
||||
const IFNAME = "eth0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "vlanTestv4",
|
||||
"type": "vlan",
|
||||
"master": "%s",
|
||||
"vlanId": 1234,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure vlan link exists in the target namespace
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
|
||||
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
newConf, err := buildOneConfig("vlanTestv4", cniVersion, n, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := json.Marshal(newConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check host-device in the target namespace
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = []byte(conf)
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure vlan link has been deleted
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
@ -21,22 +21,25 @@ import (
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/hns"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
hns.NetConf
|
||||
|
||||
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
|
||||
ApiVersion int `json:"ApiVersion"`
|
||||
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
|
||||
ApiVersion int `json:"ApiVersion"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -64,18 +67,18 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "error while ipam.ExecAdd")
|
||||
}
|
||||
|
||||
|
||||
// Convert whatever the IPAM result was into the current Result type
|
||||
result, err := current.NewResultFromResult(r)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "error while NewResultFromResult")
|
||||
} else {
|
||||
} else {
|
||||
if len(result.IPs) == 0 {
|
||||
return nil, errors.New("IPAM plugin return is missing IP config")
|
||||
}
|
||||
epInfo.IpAddress = result.IPs[0].Address.IP
|
||||
epInfo.Gateway = result.IPs[0].Address.IP.Mask(result.IPs[0].Address.Mask)
|
||||
|
||||
|
||||
// Calculate gateway for bridge network (needs to be x.2)
|
||||
epInfo.Gateway[len(epInfo.Gateway)-1] += 2
|
||||
}
|
||||
@ -149,7 +152,7 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
|
||||
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
|
||||
|
||||
hcnEndpoint, err := hns.AddHcnEndpoint(epName, hcnNetwork.Id, args.Netns, func () (*hcn.HostComputeEndpoint, error) {
|
||||
hcnEndpoint, err := hns.AddHcnEndpoint(epName, hcnNetwork.Id, args.Netns, func() (*hcn.HostComputeEndpoint, error) {
|
||||
epInfo, err := ProcessEndpointArgs(args, n)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "error while ProcessEndpointArgs")
|
||||
@ -212,7 +215,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
|
||||
|
||||
|
||||
if n.ApiVersion == 2 {
|
||||
return hns.RemoveHcnEndpoint(epName)
|
||||
} else {
|
||||
@ -226,5 +229,5 @@ func cmdGet(_ *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), bv.BuildString("win-bridge"))
|
||||
}
|
||||
|
@ -21,15 +21,17 @@ import (
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/hns"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
@ -172,5 +174,5 @@ func cmdGet(_ *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"), bv.BuildString("win-overlay"))
|
||||
}
|
||||
|
@ -35,6 +35,49 @@ import (
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *PluginConf, prevResult types.Result) (*PluginConf, []byte, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": name,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
conf := &PluginConf{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, newBytes, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("bandwidth test", func() {
|
||||
var (
|
||||
hostNs ns.NetNS
|
||||
@ -643,7 +686,6 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
tbfPluginConf := PluginConf{}
|
||||
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
@ -654,7 +696,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
tbfPluginConf.Name = "mynet"
|
||||
tbfPluginConf.CNIVersion = "0.3.0"
|
||||
tbfPluginConf.Type = "bandwidth"
|
||||
tbfPluginConf.RawPrevResult = &map[string]interface{}{
|
||||
tbfPluginConf.RawPrevResult = map[string]interface{}{
|
||||
"ips": containerWithTbfResult.IPs,
|
||||
"interfaces": containerWithTbfResult.Interfaces,
|
||||
}
|
||||
@ -663,7 +705,6 @@ var _ = Describe("bandwidth test", func() {
|
||||
IPs: containerWithTbfResult.IPs,
|
||||
Interfaces: containerWithTbfResult.Interfaces,
|
||||
}
|
||||
|
||||
conf, err := json.Marshal(tbfPluginConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -725,4 +766,169 @@ var _ = Describe("bandwidth test", func() {
|
||||
}, 1)
|
||||
})
|
||||
|
||||
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
|
||||
var ptpConf string
|
||||
var rateInBits int
|
||||
var burstInBits int
|
||||
var packetInBytes int
|
||||
var containerWithoutTbfNS ns.NetNS
|
||||
var containerWithTbfNS ns.NetNS
|
||||
var portServerWithTbf int
|
||||
var portServerWithoutTbf int
|
||||
|
||||
var containerWithTbfRes types.Result
|
||||
var containerWithoutTbfRes types.Result
|
||||
var echoServerWithTbf *gexec.Session
|
||||
var echoServerWithoutTbf *gexec.Session
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = rateInBytes * 8
|
||||
burstInBits = rateInBits * 2
|
||||
packetInBytes = rateInBytes * 25
|
||||
|
||||
ptpConf = `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "myBWnet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
|
||||
containerWithTbfIFName := "ptp0"
|
||||
containerWithoutTbfIFName := "ptp1"
|
||||
|
||||
var err error
|
||||
containerWithTbfNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutTbfNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("create two containers, and use the bandwidth plugin on one of them")
|
||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
tbfPluginConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &tbfPluginConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
}
|
||||
tbfPluginConf.Type = "bandwidth"
|
||||
cniVersion := "0.4.0"
|
||||
_, newConfBytes, err := buildOneConfig("myBWnet", cniVersion, tbfPluginConf, containerWithTbfResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithTbfNS.Path(),
|
||||
IfName: containerWithTbfIFName,
|
||||
StdinData: newConfBytes,
|
||||
}
|
||||
|
||||
result, out, err := testutils.CmdAdd(containerWithTbfNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
|
||||
// Do CNI Check
|
||||
checkConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
}
|
||||
checkConf.Type = "bandwidth"
|
||||
|
||||
_, newCheckBytes, err := buildOneConfig("myBWnet", cniVersion, checkConf, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithTbfNS.Path(),
|
||||
IfName: containerWithTbfIFName,
|
||||
StdinData: newCheckBytes,
|
||||
}
|
||||
|
||||
err = testutils.CmdCheck(containerWithTbfNS.Path(), args.ContainerID, "", newCheckBytes, func() error { return cmdCheck(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
By("starting a tcp server on both containers")
|
||||
portServerWithTbf, echoServerWithTbf, err = startEchoServerInNamespace(containerWithTbfNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
portServerWithoutTbf, echoServerWithoutTbf, err = startEchoServerInNamespace(containerWithoutTbfNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
containerWithTbfNS.Close()
|
||||
containerWithoutTbfNS.Close()
|
||||
if echoServerWithoutTbf != nil {
|
||||
echoServerWithoutTbf.Kill()
|
||||
}
|
||||
if echoServerWithTbf != nil {
|
||||
echoServerWithTbf.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
Measure("limits ingress traffic on veth device", func(b Benchmarker) {
|
||||
var runtimeWithLimit time.Duration
|
||||
var runtimeWithoutLimit time.Duration
|
||||
|
||||
By("gather timing statistics about both containers")
|
||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
||||
runtimeWithLimit = b.Time("with tbf", func() {
|
||||
result, err := current.GetResult(containerWithTbfRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithTbf, packetInBytes)
|
||||
})
|
||||
})
|
||||
|
||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
||||
runtimeWithoutLimit = b.Time("without tbf", func() {
|
||||
result, err := current.GetResult(containerWithoutTbfRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutTbf, packetInBytes)
|
||||
})
|
||||
})
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
}, 1)
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -20,13 +20,15 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
|
||||
@ -50,10 +52,6 @@ type PluginConf struct {
|
||||
Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
|
||||
// RuntimeConfig *struct{} `json:"runtimeConfig"`
|
||||
|
||||
RawPrevResult *map[string]interface{} `json:"prevResult"`
|
||||
PrevResult *current.Result `json:"-"`
|
||||
*BandwidthEntry
|
||||
}
|
||||
|
||||
@ -65,21 +63,6 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
|
||||
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
|
||||
}
|
||||
|
||||
if conf.RawPrevResult != nil {
|
||||
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
|
||||
}
|
||||
res, err := version.NewResult(conf.CNIVersion, resultBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
conf.RawPrevResult = nil
|
||||
conf.PrevResult, err = current.NewResultFromResult(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
bandwidth := getBandwidth(&conf)
|
||||
if bandwidth != nil {
|
||||
err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst)
|
||||
@ -92,6 +75,18 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if conf.RawPrevResult != nil {
|
||||
var err error
|
||||
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
|
||||
_, err = current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
|
||||
}
|
||||
@ -167,7 +162,11 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("must be called as chained plugin")
|
||||
}
|
||||
|
||||
hostInterface, err := getHostInterface(conf.PrevResult.Interfaces)
|
||||
result, err := current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
hostInterface, err := getHostInterface(result.Interfaces)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -200,7 +199,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
conf.PrevResult.Interfaces = append(conf.PrevResult.Interfaces, ¤t.Interface{
|
||||
result.Interfaces = append(result.Interfaces, ¤t.Interface{
|
||||
Name: ifbDeviceName,
|
||||
Mac: ifbDevice.Attrs().HardwareAddr.String(),
|
||||
})
|
||||
@ -210,7 +209,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
@ -232,11 +231,125 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), bv.BuildString("bandwidth"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
|
||||
qdiscs, err := netlink.QdiscList(link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := []netlink.Qdisc{}
|
||||
for _, qdisc := range qdiscs {
|
||||
// filter out pfifo_fast qdiscs because
|
||||
// older kernels don't return them
|
||||
_, pfifo := qdisc.(*netlink.PfifoFast)
|
||||
if !pfifo {
|
||||
result = append(result, qdisc)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
bwConf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bwConf.PrevResult == nil {
|
||||
return fmt.Errorf("must be called as a chained plugin")
|
||||
}
|
||||
|
||||
result, err := current.NewResultFromResult(bwConf.PrevResult)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link, err := netlink.LinkByName(hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bandwidth := getBandwidth(bwConf)
|
||||
|
||||
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
||||
rateInBytes := bandwidth.IngressRate / 8
|
||||
burstInBytes := bandwidth.IngressBurst / 8
|
||||
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
|
||||
|
||||
qdiscs, err := SafeQdiscList(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(qdiscs) == 0 {
|
||||
return fmt.Errorf("Failed to find qdisc")
|
||||
}
|
||||
|
||||
for _, qdisc := range qdiscs {
|
||||
tbf, isTbf := qdisc.(*netlink.Tbf)
|
||||
if !isTbf {
|
||||
break
|
||||
}
|
||||
if tbf.Rate != uint64(rateInBytes) {
|
||||
return fmt.Errorf("Rate doesn't match")
|
||||
}
|
||||
if tbf.Limit != uint32(limitInBytes) {
|
||||
return fmt.Errorf("Limit doesn't match")
|
||||
}
|
||||
if tbf.Buffer != uint32(bufferInBytes) {
|
||||
return fmt.Errorf("Buffer doesn't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
||||
rateInBytes := bandwidth.EgressRate / 8
|
||||
burstInBytes := bandwidth.EgressBurst / 8
|
||||
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get ifb device: %s", err)
|
||||
}
|
||||
|
||||
qdiscs, err := SafeQdiscList(ifbDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(qdiscs) == 0 {
|
||||
return fmt.Errorf("Failed to find qdisc")
|
||||
}
|
||||
|
||||
for _, qdisc := range qdiscs {
|
||||
tbf, isTbf := qdisc.(*netlink.Tbf)
|
||||
if !isTbf {
|
||||
break
|
||||
}
|
||||
if tbf.Rate != uint64(rateInBytes) {
|
||||
return fmt.Errorf("Rate doesn't match")
|
||||
}
|
||||
if tbf.Limit != uint32(limitInBytes) {
|
||||
return fmt.Errorf("Limit doesn't match")
|
||||
}
|
||||
if tbf.Buffer != uint32(bufferInBytes) {
|
||||
return fmt.Errorf("Buffer doesn't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
135
plugins/meta/firewall/README.md
Normal file
135
plugins/meta/firewall/README.md
Normal file
@ -0,0 +1,135 @@
|
||||
# firewall plugin
|
||||
|
||||
## Overview
|
||||
|
||||
This plugin creates firewall rules to allow traffic to/from container IP address via the host network .
|
||||
It does not create any network interfaces and therefore does not set up connectivity by itself.
|
||||
It is intended to be used as a chained plugins.
|
||||
|
||||
## Operation
|
||||
The following network configuration file
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "bridge-firewalld",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.88.0.0/16",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
will allow any IP addresses configured by earlier plugins to send/receive traffic via the host.
|
||||
|
||||
A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that previous result.
|
||||
|
||||
## Backends
|
||||
|
||||
This plugin supports multiple firewall backends that implement the desired functionality.
|
||||
Available backends include `iptables` and `firewalld` and may be selected with the `backend` key.
|
||||
If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus.
|
||||
If no firewalld service is found, it will fall back to iptables.
|
||||
|
||||
## firewalld backend rule structure
|
||||
When the `firewalld` backend is used, this example will place the IPAM allocated address for the container (e.g. 10.88.0.2) into firewalld's `trusted` zone, allowing it to send/receive traffic.
|
||||
|
||||
|
||||
A sample standalone config list (with the file extension .conflist) using firewalld backend might
|
||||
look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "bridge-firewalld",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.88.0.0/16",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "firewalld"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
`FORWARD_IN_ZONES_SOURCE` chain:
|
||||
- `-d 10.88.0.2 -j FWDI_trusted`
|
||||
|
||||
`CNI_FORWARD_OUT_ZONES_SOURCE` chain:
|
||||
- `-s 10.88.0.2 -j FWDO_trusted`
|
||||
|
||||
|
||||
## iptables backend rule structure
|
||||
|
||||
A sample standalone config list (with the file extension .conflist) using iptables backend might
|
||||
look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "bridge-firewalld",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.88.0.0/16",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "iptables"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic.
|
||||
|
||||
### FORWARD
|
||||
A new chain, CNI-FORWARD is added to the FORWARD chain. CNI-FORWARD is the chain where rules will be added
|
||||
when containers are created and from where rules will be removed when containers terminate.
|
||||
|
||||
`FORWARD` chain:
|
||||
- `-j CNI-FORWARD`
|
||||
|
||||
CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address
|
||||
of the container as shown:
|
||||
|
||||
`CNI_FORWARD` chain:
|
||||
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD`
|
||||
- `-d 10.88.0.2 -j CNI-FORWARD`
|
||||
|
184
plugins/meta/firewall/firewall.go
Normal file
184
plugins/meta/firewall/firewall.go
Normal file
@ -0,0 +1,184 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// This is a "meta-plugin". It reads in its own netconf, it does not create
|
||||
// any network interface but just changes the network sysctl.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// FirewallNetConf represents the firewall configuration.
|
||||
type FirewallNetConf struct {
|
||||
types.NetConf
|
||||
|
||||
// Backend is the firewall type to add rules to. Allowed values are
|
||||
// 'iptables' and 'firewalld'.
|
||||
Backend string `json:"backend"`
|
||||
|
||||
// IptablesAdminChainName is an optional name to use instead of the default
|
||||
// admin rules override chain name that includes the interface name.
|
||||
IptablesAdminChainName string `json:"iptablesAdminChainName,omitempty"`
|
||||
|
||||
// FirewalldZone is an optional firewalld zone to place the interface into. If
|
||||
// the firewalld backend is used but the zone is not given, it defaults
|
||||
// to 'trusted'
|
||||
FirewalldZone string `json:"firewalldZone,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallBackend interface {
|
||||
Add(*FirewallNetConf, *current.Result) error
|
||||
Del(*FirewallNetConf, *current.Result) error
|
||||
Check(*FirewallNetConf, *current.Result) error
|
||||
}
|
||||
|
||||
func ipString(ip net.IPNet) string {
|
||||
if ip.IP.To4() == nil {
|
||||
return ip.IP.String() + "/128"
|
||||
}
|
||||
return ip.IP.String() + "/32"
|
||||
}
|
||||
|
||||
func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
|
||||
conf := FirewallNetConf{}
|
||||
if err := json.Unmarshal(data, &conf); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if conf.RawPrevResult == nil {
|
||||
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
var result *current.Result
|
||||
var err error
|
||||
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
|
||||
result, err = current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
// Default the firewalld zone to trusted
|
||||
if conf.FirewalldZone == "" {
|
||||
conf.FirewalldZone = "trusted"
|
||||
}
|
||||
|
||||
return &conf, result, nil
|
||||
}
|
||||
|
||||
func getBackend(conf *FirewallNetConf) (FirewallBackend, error) {
|
||||
switch conf.Backend {
|
||||
case "iptables":
|
||||
return newIptablesBackend(conf)
|
||||
case "firewalld":
|
||||
return newFirewalldBackend(conf)
|
||||
}
|
||||
|
||||
// Default to firewalld if it's running
|
||||
if isFirewalldRunning() {
|
||||
return newFirewalldBackend(conf)
|
||||
}
|
||||
|
||||
// Otherwise iptables
|
||||
return newIptablesBackend(conf)
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
conf, result, err := parseConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := backend.Add(conf, result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
result = ¤t.Result{}
|
||||
}
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
conf, result, err := parseConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tolerate errors if the container namespace has been torn down already
|
||||
containerNS, err := ns.GetNS(args.Netns)
|
||||
if err == nil {
|
||||
defer containerNS.Close()
|
||||
}
|
||||
|
||||
// Runtime errors are ignored
|
||||
if err := backend.Del(conf, result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.4.0"), bv.BuildString("firewall"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf, result, err := parseConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure we have previous result.
|
||||
if result == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := backend.Check(conf, result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
343
plugins/meta/firewall/firewall_firewalld_test.go
Normal file
343
plugins/meta/firewall/firewall_firewalld_test.go
Normal file
@ -0,0 +1,343 @@
|
||||
// Copyright 2018 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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
"github.com/godbus/dbus"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
confTmpl = `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "firewalld-test",
|
||||
"type": "firewall",
|
||||
"backend": "firewalld",
|
||||
"zone": "trusted",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.0",
|
||||
"interfaces": [
|
||||
{"name": "%s", "sandbox": "%s"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
ifname = "eth0"
|
||||
)
|
||||
|
||||
type fakeFirewalld struct {
|
||||
zone string
|
||||
source string
|
||||
}
|
||||
|
||||
func (f *fakeFirewalld) clear() {
|
||||
f.zone = ""
|
||||
f.source = ""
|
||||
}
|
||||
|
||||
func (f *fakeFirewalld) AddSource(zone, source string) (string, *dbus.Error) {
|
||||
f.zone = zone
|
||||
f.source = source
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) {
|
||||
f.zone = zone
|
||||
f.source = source
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *fakeFirewalld) QuerySource(zone, source string) (bool, *dbus.Error) {
|
||||
if f.zone != zone {
|
||||
return false, nil
|
||||
}
|
||||
if f.source != source {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) {
|
||||
// Start a private D-Bus session bus
|
||||
path, err := invoke.FindInPath("dbus-daemon", []string{
|
||||
"/bin", "/sbin", "/usr/bin", "/usr/sbin",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cmd := exec.Command(path, "--session", "--print-address", "--nofork", "--nopidfile")
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = cmd.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Wait for dbus-daemon to print the bus address
|
||||
bytes, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
busAddr := strings.TrimSpace(string(bytes))
|
||||
Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue())
|
||||
|
||||
var startWg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
startWg.Add(1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
startWg.Done()
|
||||
err = cmd.Wait()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
startWg.Wait()
|
||||
return busAddr, cmd
|
||||
}
|
||||
|
||||
var _ = Describe("firewalld test", func() {
|
||||
var (
|
||||
targetNs ns.NetNS
|
||||
cmd *exec.Cmd
|
||||
conn *dbus.Conn
|
||||
wg sync.WaitGroup
|
||||
fwd *fakeFirewalld
|
||||
busAddr string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
targetNs, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Start a private D-Bus session bus
|
||||
busAddr, cmd = spawnSessionDbus(&wg)
|
||||
conn, err = dbus.Dial(busAddr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = conn.Auth(nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = conn.Hello()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Start our fake firewalld
|
||||
reply, err := conn.RequestName(firewalldName, dbus.NameFlagDoNotQueue)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(reply).To(Equal(dbus.RequestNameReplyPrimaryOwner))
|
||||
|
||||
fwd = &fakeFirewalld{}
|
||||
// Because firewalld D-Bus methods start with lower-case, and
|
||||
// because in Go lower-case methods are private, we need to remap
|
||||
// Go public methods to the D-Bus name
|
||||
methods := map[string]string{
|
||||
"AddSource": firewalldAddSourceMethod,
|
||||
"QuerySource": firewalldQuerySourceMethod,
|
||||
"RemoveSource": firewalldRemoveSourceMethod,
|
||||
}
|
||||
conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface)
|
||||
|
||||
// Make sure the plugin uses our private session bus
|
||||
testConn = conn
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_, err := conn.ReleaseName(firewalldName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
It("works with a 0.3.1 config", func() {
|
||||
Expect(isFirewalldRunning()).To(BeTrue())
|
||||
|
||||
conf := fmt.Sprintf(confTmpl, ifname, targetNs.Path())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
_, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(fwd.zone).To(Equal("trusted"))
|
||||
Expect(fwd.source).To(Equal("10.0.0.2/32"))
|
||||
fwd.clear()
|
||||
|
||||
err = testutils.CmdDel(targetNs.Path(), args.ContainerID, ifname, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(fwd.zone).To(Equal("trusted"))
|
||||
Expect(fwd.source).To(Equal("10.0.0.2/32"))
|
||||
})
|
||||
|
||||
It("defaults to the firewalld backend", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "firewalld-test",
|
||||
"type": "firewall",
|
||||
"zone": "trusted",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.0",
|
||||
"interfaces": [
|
||||
{"name": "eth0", "sandbox": "/foobar"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
Expect(isFirewalldRunning()).To(BeTrue())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
_, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(fwd.zone).To(Equal("trusted"))
|
||||
Expect(fwd.source).To(Equal("10.0.0.2/32"))
|
||||
})
|
||||
|
||||
It("passes through the prevResult", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "firewalld-test",
|
||||
"type": "firewall",
|
||||
"zone": "trusted",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.0",
|
||||
"interfaces": [
|
||||
{"name": "eth0", "sandbox": "/foobar"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
Expect(isFirewalldRunning()).To(BeTrue())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
r, _, err := testutils.CmdAdd(targetNs.Path(), args.ContainerID, ifname, []byte(conf), func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal("eth0"))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
})
|
||||
|
||||
It("works with a 0.4.0 config, including Check", func() {
|
||||
Expect(isFirewalldRunning()).To(BeTrue())
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "firewalld-test",
|
||||
"type": "firewall",
|
||||
"backend": "firewalld",
|
||||
"zone": "trusted",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.4.0",
|
||||
"interfaces": [
|
||||
{"name": "eth0", "sandbox": "/foobar"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(fwd.zone).To(Equal("trusted"))
|
||||
Expect(fwd.source).To(Equal("10.0.0.2/32"))
|
||||
|
||||
_, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(fwd.zone).To(Equal("trusted"))
|
||||
Expect(fwd.source).To(Equal("10.0.0.2/32"))
|
||||
})
|
||||
})
|
512
plugins/meta/firewall/firewall_iptables_test.go
Normal file
512
plugins/meta/firewall/firewall_iptables_test.go
Normal file
@ -0,0 +1,512 @@
|
||||
// Copyright 2017 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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func findChains(chains []string) (bool, bool) {
|
||||
var foundAdmin, foundPriv bool
|
||||
for _, ch := range chains {
|
||||
if ch == "CNI-ADMIN" {
|
||||
foundAdmin = true
|
||||
} else if ch == "CNI-FORWARD" {
|
||||
foundPriv = true
|
||||
}
|
||||
}
|
||||
return foundAdmin, foundPriv
|
||||
}
|
||||
|
||||
func findForwardJumpRules(rules []string) (bool, bool) {
|
||||
var foundAdmin, foundPriv bool
|
||||
for _, rule := range rules {
|
||||
if strings.Contains(rule, "-j CNI-ADMIN") {
|
||||
foundAdmin = true
|
||||
} else if strings.Contains(rule, "-j CNI-FORWARD") {
|
||||
foundPriv = true
|
||||
}
|
||||
}
|
||||
return foundAdmin, foundPriv
|
||||
}
|
||||
|
||||
func findForwardAllowRules(rules []string, ip string) (bool, bool) {
|
||||
var foundOne, foundTwo bool
|
||||
for _, rule := range rules {
|
||||
if !strings.HasSuffix(rule, "-j ACCEPT") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(rule, fmt.Sprintf(" -s %s ", ip)) {
|
||||
foundOne = true
|
||||
} else if strings.Contains(rule, fmt.Sprintf(" -d %s ", ip)) && strings.Contains(rule, "RELATED,ESTABLISHED") {
|
||||
foundTwo = true
|
||||
}
|
||||
}
|
||||
return foundOne, foundTwo
|
||||
}
|
||||
|
||||
func getPrevResult(bytes []byte) *current.Result {
|
||||
type TmpConf struct {
|
||||
types.NetConf
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult *current.Result `json:"-"`
|
||||
}
|
||||
|
||||
conf := &TmpConf{}
|
||||
err := json.Unmarshal(bytes, conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if conf.RawPrevResult == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
res, err := version.NewResult(conf.CNIVersion, resultBytes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
prevResult, err := current.NewResultFromResult(res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return prevResult
|
||||
}
|
||||
|
||||
func validateFullRuleset(bytes []byte) {
|
||||
prevResult := getPrevResult(bytes)
|
||||
|
||||
for _, ip := range prevResult.IPs {
|
||||
ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Ensure chains
|
||||
chains, err := ipt.ListChains("filter")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
foundAdmin, foundPriv := findChains(chains)
|
||||
Expect(foundAdmin).To(Equal(true))
|
||||
Expect(foundPriv).To(Equal(true))
|
||||
|
||||
// Look for the FORWARD chain jump rules to our custom chains
|
||||
rules, err := ipt.List("filter", "FORWARD")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(rules)).Should(BeNumerically(">", 1))
|
||||
_, foundPriv = findForwardJumpRules(rules)
|
||||
Expect(foundPriv).To(Equal(true))
|
||||
|
||||
// Look for the allow rules in our custom FORWARD chain
|
||||
rules, err = ipt.List("filter", "CNI-FORWARD")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(rules)).Should(BeNumerically(">", 1))
|
||||
foundAdmin, _ = findForwardJumpRules(rules)
|
||||
Expect(foundAdmin).To(Equal(true))
|
||||
|
||||
// Look for the IP allow rules
|
||||
foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address))
|
||||
Expect(foundOne).To(Equal(true))
|
||||
Expect(foundTwo).To(Equal(true))
|
||||
}
|
||||
}
|
||||
|
||||
func validateCleanedUp(bytes []byte) {
|
||||
prevResult := getPrevResult(bytes)
|
||||
|
||||
for _, ip := range prevResult.IPs {
|
||||
ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Our private and admin chains don't get cleaned up
|
||||
chains, err := ipt.ListChains("filter")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
foundAdmin, foundPriv := findChains(chains)
|
||||
Expect(foundAdmin).To(Equal(true))
|
||||
Expect(foundPriv).To(Equal(true))
|
||||
|
||||
// Look for the FORWARD chain jump rules to our custom chains
|
||||
rules, err := ipt.List("filter", "FORWARD")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, foundPriv = findForwardJumpRules(rules)
|
||||
Expect(foundPriv).To(Equal(true))
|
||||
|
||||
// Look for the allow rules in our custom FORWARD chain
|
||||
rules, err = ipt.List("filter", "CNI-FORWARD")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
foundAdmin, _ = findForwardJumpRules(rules)
|
||||
Expect(foundAdmin).To(Equal(true))
|
||||
|
||||
// Expect no IP address rules for this IP
|
||||
foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address))
|
||||
Expect(foundOne).To(Equal(false))
|
||||
Expect(foundTwo).To(Equal(false))
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("firewall plugin iptables backend", func() {
|
||||
var originalNS, targetNS ns.NetNS
|
||||
const IFNAME string = "dummy0"
|
||||
|
||||
fullConf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "firewall",
|
||||
"backend": "iptables",
|
||||
"ifName": "dummy0",
|
||||
"cniVersion": "0.3.1",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"interface": 0
|
||||
},
|
||||
{
|
||||
"version": "6",
|
||||
"address": "2001:db8:1:2::1/64",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a new NetNS so we don't modify the host
|
||||
var err error
|
||||
originalNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IFNAME,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
targetNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(targetNS.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
It("passes prevResult through unchanged", func() {
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: fullConf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
Expect(result.IPs[1].Address.String()).To(Equal("2001:db8:1:2::1/64"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("installs the right iptables rules on the host", func() {
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: fullConf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
validateFullRuleset(fullConf)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("correctly handles a custom IptablesAdminChainName", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "firewall",
|
||||
"backend": "iptables",
|
||||
"ifName": "dummy0",
|
||||
"cniVersion": "0.3.1",
|
||||
"iptablesAdminChainName": "CNI-foobar",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"interface": 0
|
||||
},
|
||||
{
|
||||
"version": "6",
|
||||
"address": "2001:db8:1:2::1/64",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, conf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} {
|
||||
ipt, err = iptables.NewWithProtocol(proto)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Ensure custom admin chain name
|
||||
chains, err := ipt.ListChains("filter")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var foundAdmin bool
|
||||
for _, ch := range chains {
|
||||
if ch == "CNI-foobar" {
|
||||
foundAdmin = true
|
||||
}
|
||||
}
|
||||
Expect(foundAdmin).To(Equal(true))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("cleans up on delete", func() {
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: fullConf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
validateFullRuleset(fullConf)
|
||||
|
||||
err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
validateCleanedUp(fullConf)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("installs the right iptables rules on the host v4.0.x and check is successful", func() {
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: fullConf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
validateFullRuleset(fullConf)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("cleans up on delete v4.0.x", func() {
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: fullConf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
validateFullRuleset(fullConf)
|
||||
|
||||
err = testutils.CmdDel(targetNS.Path(), args.ContainerID, IFNAME, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
validateCleanedUp(fullConf)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("firewall plugin iptables backend v0.4.x", func() {
|
||||
var originalNS, targetNS ns.NetNS
|
||||
const IFNAME string = "dummy0"
|
||||
|
||||
fullConf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "firewall",
|
||||
"backend": "iptables",
|
||||
"ifName": "dummy0",
|
||||
"cniVersion": "0.4.0",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"interface": 0
|
||||
},
|
||||
{
|
||||
"version": "6",
|
||||
"address": "2001:db8:1:2::1/64",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a new NetNS so we don't modify the host
|
||||
var err error
|
||||
originalNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IFNAME,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
targetNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(targetNS.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
It("installs iptables rules, Check rules then cleans up on delete using v4.0.x", func() {
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: fullConf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
validateFullRuleset(fullConf)
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
validateCleanedUp(fullConf)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
27
plugins/meta/firewall/firewall_suite_test.go
Normal file
27
plugins/meta/firewall/firewall_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017 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 (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFirewall(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "firewall Suite")
|
||||
}
|
122
plugins/meta/firewall/firewalld.go
Normal file
122
plugins/meta/firewall/firewalld.go
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright 2018 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/godbus/dbus"
|
||||
)
|
||||
|
||||
const (
|
||||
dbusName = "org.freedesktop.DBus"
|
||||
dbusPath = "/org/freedesktop/DBus"
|
||||
dbusGetNameOwnerMethod = "GetNameOwner"
|
||||
|
||||
firewalldName = "org.fedoraproject.FirewallD1"
|
||||
firewalldPath = "/org/fedoraproject/FirewallD1"
|
||||
firewalldZoneInterface = "org.fedoraproject.FirewallD1.zone"
|
||||
firewalldAddSourceMethod = "addSource"
|
||||
firewalldRemoveSourceMethod = "removeSource"
|
||||
firewalldQuerySourceMethod = "querySource"
|
||||
|
||||
errZoneAlreadySet = "ZONE_ALREADY_SET"
|
||||
)
|
||||
|
||||
// Only used for testcases to override the D-Bus connection
|
||||
var testConn *dbus.Conn
|
||||
|
||||
type fwdBackend struct {
|
||||
conn *dbus.Conn
|
||||
}
|
||||
|
||||
// fwdBackend implements the FirewallBackend interface
|
||||
var _ FirewallBackend = &fwdBackend{}
|
||||
|
||||
func getConn() (*dbus.Conn, error) {
|
||||
if testConn != nil {
|
||||
return testConn, nil
|
||||
}
|
||||
return dbus.SystemBus()
|
||||
}
|
||||
|
||||
// isFirewalldRunning checks whether firewalld is running.
|
||||
func isFirewalldRunning() bool {
|
||||
conn, err := getConn()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
dbusObj := conn.Object(dbusName, dbusPath)
|
||||
var res string
|
||||
if err := dbusObj.Call(dbusName+"."+dbusGetNameOwnerMethod, 0, firewalldName).Store(&res); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func newFirewalldBackend(conf *FirewallNetConf) (FirewallBackend, error) {
|
||||
conn, err := getConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backend := &fwdBackend{
|
||||
conn: conn,
|
||||
}
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (fb *fwdBackend) Add(conf *FirewallNetConf, result *current.Result) error {
|
||||
for _, ip := range result.IPs {
|
||||
ipStr := ipString(ip.Address)
|
||||
// Add a firewalld rule which assigns the given source IP to the given zone
|
||||
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
|
||||
var res string
|
||||
if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldAddSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil {
|
||||
if !strings.Contains(err.Error(), errZoneAlreadySet) {
|
||||
return fmt.Errorf("failed to add the address %v to %v zone: %v", ipStr, conf.FirewalldZone, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fb *fwdBackend) Del(conf *FirewallNetConf, result *current.Result) error {
|
||||
for _, ip := range result.IPs {
|
||||
ipStr := ipString(ip.Address)
|
||||
// Remove firewalld rules which assigned the given source IP to the given zone
|
||||
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
|
||||
var res string
|
||||
firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fb *fwdBackend) Check(conf *FirewallNetConf, result *current.Result) error {
|
||||
for _, ip := range result.IPs {
|
||||
ipStr := ipString(ip.Address)
|
||||
// Check for a firewalld rule for the given source IP to the given zone
|
||||
firewalldObj := fb.conn.Object(firewalldName, firewalldPath)
|
||||
var res bool
|
||||
if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldQuerySourceMethod, 0, conf.FirewalldZone, ipStr).Store(&res); err != nil {
|
||||
return fmt.Errorf("failed to find the address %v in %v zone", ipStr, conf.FirewalldZone)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
279
plugins/meta/firewall/iptables.go
Normal file
279
plugins/meta/firewall/iptables.go
Normal file
@ -0,0 +1,279 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// This is a "meta-plugin". It reads in its own netconf, it does not create
|
||||
// any network interface but just changes the network sysctl.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
func getPrivChainRules(ip string) [][]string {
|
||||
var rules [][]string
|
||||
rules = append(rules, []string{"-d", ip, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"})
|
||||
rules = append(rules, []string{"-s", ip, "-j", "ACCEPT"})
|
||||
return rules
|
||||
}
|
||||
|
||||
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
chains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ipt.NewChain(table, chain)
|
||||
}
|
||||
|
||||
func generateFilterRule(privChainName string) []string {
|
||||
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
|
||||
}
|
||||
|
||||
func generateAdminRule(adminChainName string) []string {
|
||||
return []string{"-m", "comment", "--comment", "CNI firewall plugin admin overrides", "-j", adminChainName}
|
||||
}
|
||||
|
||||
func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) {
|
||||
for _, rule := range rules {
|
||||
ipt.Delete("filter", privChainName, rule...)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) error {
|
||||
exists, err := ipt.Exists("filter", chain, rule...)
|
||||
if !exists && err == nil {
|
||||
err = ipt.Insert("filter", chain, 1, rule...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
|
||||
privRule := generateFilterRule(ib.privChainName)
|
||||
adminRule := generateFilterRule(ib.adminChainName)
|
||||
|
||||
// Ensure our private chains exist
|
||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure our filter rule exists in the forward chain
|
||||
if err := ensureFirstChainRule(ipt, "FORWARD", privRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure our admin override chain rule exists in our private chain
|
||||
if err := ensureFirstChainRule(ipt, ib.privChainName, adminRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func protoForIP(ip net.IPNet) iptables.Protocol {
|
||||
if ip.IP.To4() != nil {
|
||||
return iptables.ProtocolIPv4
|
||||
}
|
||||
return iptables.ProtocolIPv6
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) addRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error {
|
||||
rules := make([][]string, 0)
|
||||
for _, ip := range result.IPs {
|
||||
if protoForIP(ip.Address) == proto {
|
||||
rules = append(rules, getPrivChainRules(ipString(ip.Address))...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rules) > 0 {
|
||||
if err := ib.setupChains(ipt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up on any errors
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
cleanupRules(ipt, ib.privChainName, rules)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, rule := range rules {
|
||||
err = ipt.AppendUnique("filter", ib.privChainName, rule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) delRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error {
|
||||
rules := make([][]string, 0)
|
||||
for _, ip := range result.IPs {
|
||||
if protoForIP(ip.Address) == proto {
|
||||
rules = append(rules, getPrivChainRules(ipString(ip.Address))...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rules) > 0 {
|
||||
cleanupRules(ipt, ib.privChainName, rules)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Result, ipt *iptables.IPTables, proto iptables.Protocol) error {
|
||||
rules := make([][]string, 0)
|
||||
for _, ip := range result.IPs {
|
||||
if protoForIP(ip.Address) == proto {
|
||||
rules = append(rules, getPrivChainRules(ipString(ip.Address))...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rules) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure our private chains exist
|
||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure our filter rule exists in the forward chain
|
||||
privRule := generateFilterRule(ib.privChainName)
|
||||
privExists, err := ipt.Exists("filter", "FORWARD", privRule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !privExists {
|
||||
return fmt.Errorf("expected %v rule %v not found", "FORWARD", privRule)
|
||||
}
|
||||
|
||||
// Ensure our admin override chain rule exists in our private chain
|
||||
adminRule := generateFilterRule(ib.adminChainName)
|
||||
adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !adminExists {
|
||||
return fmt.Errorf("expected %v rule %v not found", ib.privChainName, adminRule)
|
||||
}
|
||||
|
||||
// ensure rules for this IP address exist
|
||||
for _, rule := range rules {
|
||||
// Ensure our rule exists in our private chain
|
||||
exists, err := ipt.Exists("filter", ib.privChainName, rule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("expected rule %v not found", rule)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findProtos(conf *FirewallNetConf) []iptables.Protocol {
|
||||
protos := []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6}
|
||||
if conf.PrevResult != nil {
|
||||
// If PrevResult is given, scan all IP addresses to figure out
|
||||
// which IP versions to use
|
||||
protos = []iptables.Protocol{}
|
||||
result, _ := current.NewResultFromResult(conf.PrevResult)
|
||||
for _, addr := range result.IPs {
|
||||
if addr.Address.IP.To4() != nil {
|
||||
protos = append(protos, iptables.ProtocolIPv4)
|
||||
} else {
|
||||
protos = append(protos, iptables.ProtocolIPv6)
|
||||
}
|
||||
}
|
||||
}
|
||||
return protos
|
||||
}
|
||||
|
||||
type iptablesBackend struct {
|
||||
protos map[iptables.Protocol]*iptables.IPTables
|
||||
privChainName string
|
||||
adminChainName string
|
||||
ifName string
|
||||
}
|
||||
|
||||
// iptablesBackend implements the FirewallBackend interface
|
||||
var _ FirewallBackend = &iptablesBackend{}
|
||||
|
||||
func newIptablesBackend(conf *FirewallNetConf) (FirewallBackend, error) {
|
||||
adminChainName := conf.IptablesAdminChainName
|
||||
if adminChainName == "" {
|
||||
adminChainName = "CNI-ADMIN"
|
||||
}
|
||||
|
||||
backend := &iptablesBackend{
|
||||
privChainName: "CNI-FORWARD",
|
||||
adminChainName: adminChainName,
|
||||
protos: make(map[iptables.Protocol]*iptables.IPTables),
|
||||
}
|
||||
|
||||
for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} {
|
||||
ipt, err := iptables.NewWithProtocol(proto)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize iptables protocol %v: %v", proto, err)
|
||||
}
|
||||
backend.protos[proto] = ipt
|
||||
}
|
||||
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) Add(conf *FirewallNetConf, result *current.Result) error {
|
||||
for proto, ipt := range ib.protos {
|
||||
if err := ib.addRules(conf, result, ipt, proto); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) Del(conf *FirewallNetConf, result *current.Result) error {
|
||||
for proto, ipt := range ib.protos {
|
||||
ib.delRules(conf, result, ipt, proto)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *iptablesBackend) Check(conf *FirewallNetConf, result *current.Result) error {
|
||||
for proto, ipt := range ib.protos {
|
||||
if err := ib.checkRules(conf, result, ipt, proto); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -34,6 +34,8 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -217,7 +219,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, bv.BuildString("flannel"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
|
@ -29,6 +29,8 @@ type chain struct {
|
||||
|
||||
entryRules [][]string // the rules that "point" to this chain
|
||||
rules [][]string // the rules this chain contains
|
||||
|
||||
prependEntry bool // whether or not the entry rules should be prepended
|
||||
}
|
||||
|
||||
// setup idempotently creates the chain. It will not error if the chain exists.
|
||||
@ -45,19 +47,19 @@ func (c *chain) setup(ipt *iptables.IPTables) error {
|
||||
}
|
||||
|
||||
// Add the rules to the chain
|
||||
for i := len(c.rules) - 1; i >= 0; i-- {
|
||||
if err := prependUnique(ipt, c.table, c.name, c.rules[i]); err != nil {
|
||||
for _, rule := range c.rules {
|
||||
if err := insertUnique(ipt, c.table, c.name, false, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the entry rules to the entry chains
|
||||
for _, entryChain := range c.entryChains {
|
||||
for i := len(c.entryRules) - 1; i >= 0; i-- {
|
||||
for _, rule := range c.entryRules {
|
||||
r := []string{}
|
||||
r = append(r, c.entryRules[i]...)
|
||||
r = append(r, rule...)
|
||||
r = append(r, "-j", c.name)
|
||||
if err := prependUnique(ipt, c.table, entryChain, r); err != nil {
|
||||
if err := insertUnique(ipt, c.table, entryChain, c.prependEntry, r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -105,8 +107,9 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prependUnique will prepend a rule to a chain, if it does not already exist
|
||||
func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) error {
|
||||
// insertUnique will add a rule to a chain if it does not already exist.
|
||||
// By default the rule is appended, unless prepend is true.
|
||||
func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rule []string) error {
|
||||
exists, err := ipt.Exists(table, chain, rule...)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -115,7 +118,11 @@ func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
return ipt.Insert(table, chain, 1, rule...)
|
||||
if prepend {
|
||||
return ipt.Insert(table, chain, 1, rule...)
|
||||
} else {
|
||||
return ipt.Append(table, chain, rule...)
|
||||
}
|
||||
}
|
||||
|
||||
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
|
||||
@ -131,3 +138,44 @@ func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check the chain.
|
||||
func (c *chain) check(ipt *iptables.IPTables) error {
|
||||
|
||||
exists, err := chainExists(ipt, c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("chain %s not found in iptables table %s", c.name, c.table)
|
||||
}
|
||||
|
||||
for i := len(c.rules) - 1; i >= 0; i-- {
|
||||
match := checkRule(ipt, c.table, c.name, c.rules[i])
|
||||
if !match {
|
||||
return fmt.Errorf("rule %s in chain %s not found in table %s", c.rules, c.name, c.table)
|
||||
}
|
||||
}
|
||||
|
||||
for _, entryChain := range c.entryChains {
|
||||
for i := len(c.entryRules) - 1; i >= 0; i-- {
|
||||
r := []string{}
|
||||
r = append(r, c.entryRules[i]...)
|
||||
r = append(r, "-j", c.name)
|
||||
matchEntryChain := checkRule(ipt, c.table, entryChain, r)
|
||||
if !matchEntryChain {
|
||||
return fmt.Errorf("rule %s in chain %s not found in table %s", c.entryRules, entryChain, c.table)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRule(ipt *iptables.IPTables, table, chain string, rule []string) bool {
|
||||
exists, err := ipt.Exists(table, chain, rule...)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
@ -117,8 +117,8 @@ var _ = Describe("chain tests", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(haveRules).To(Equal([]string{
|
||||
"-N " + tlChainName,
|
||||
"-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name,
|
||||
"-A " + tlChainName + ` -m comment --comment "canary value" -j ACCEPT`,
|
||||
"-A " + tlChainName + " -d 203.0.113.1/32 -j " + testChain.name,
|
||||
}))
|
||||
|
||||
// Check that the chain and rule was created
|
||||
|
@ -34,6 +34,8 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// PortMapEntry corresponds to a single entry in the port_mappings argument,
|
||||
@ -55,8 +57,6 @@ type PortMapConf struct {
|
||||
RuntimeConfig struct {
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult *current.Result `json:"-"`
|
||||
|
||||
// These are fields parsed out of the config or the environment;
|
||||
// included here for convenience
|
||||
@ -70,7 +70,7 @@ type PortMapConf struct {
|
||||
const DefaultMarkBit = 13
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
netConf, err := parseConfig(args.StdinData, args.IfName)
|
||||
netConf, _, err := parseConfig(args.StdinData, args.IfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
@ -102,7 +102,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
netConf, err := parseConfig(args.StdinData, args.IfName)
|
||||
netConf, _, err := parseConfig(args.StdinData, args.IfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
@ -118,37 +118,60 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("portmap"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf, result, err := parseConfig(args.StdinData, args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure we have previous result.
|
||||
if result == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if len(conf.RuntimeConfig.PortMaps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
conf.ContainerID = args.ContainerID
|
||||
|
||||
if conf.ContIPv4 != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if conf.ContIPv6 != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseConfig parses the supplied configuration (and prevResult) from stdin.
|
||||
func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
|
||||
func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, error) {
|
||||
conf := PortMapConf{}
|
||||
|
||||
if err := json.Unmarshal(stdin, &conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse network configuration: %v", err)
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
var result *current.Result
|
||||
if conf.RawPrevResult != nil {
|
||||
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
|
||||
var err error
|
||||
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
res, err := version.NewResult(conf.CNIVersion, resultBytes)
|
||||
|
||||
result, err = current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
conf.RawPrevResult = nil
|
||||
conf.PrevResult, err = current.NewResultFromResult(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +181,7 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
|
||||
}
|
||||
|
||||
if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil {
|
||||
return nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
|
||||
return nil, nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
|
||||
}
|
||||
|
||||
if conf.MarkMasqBit == nil {
|
||||
@ -167,21 +190,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
|
||||
}
|
||||
|
||||
if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 {
|
||||
return nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
|
||||
return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
|
||||
}
|
||||
|
||||
// Reject invalid port numbers
|
||||
for _, pm := range conf.RuntimeConfig.PortMaps {
|
||||
if pm.ContainerPort <= 0 {
|
||||
return nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
|
||||
return nil, nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
|
||||
}
|
||||
if pm.HostPort <= 0 {
|
||||
return nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
|
||||
return nil, nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.PrevResult != nil {
|
||||
for _, ip := range conf.PrevResult.IPs {
|
||||
for _, ip := range result.IPs {
|
||||
if ip.Version == "6" && conf.ContIPv6 != nil {
|
||||
continue
|
||||
} else if ip.Version == "4" && conf.ContIPv4 != nil {
|
||||
@ -192,9 +215,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
|
||||
if ip.Interface != nil {
|
||||
intIdx := *ip.Interface
|
||||
if intIdx >= 0 &&
|
||||
intIdx < len(conf.PrevResult.Interfaces) &&
|
||||
(conf.PrevResult.Interfaces[intIdx].Name != ifName ||
|
||||
conf.PrevResult.Interfaces[intIdx].Sandbox == "") {
|
||||
intIdx < len(result.Interfaces) &&
|
||||
(result.Interfaces[intIdx].Name != ifName ||
|
||||
result.Interfaces[intIdx].Sandbox == "") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -207,5 +230,5 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
return &conf, result, nil
|
||||
}
|
||||
|
@ -111,6 +111,46 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
fillDnatRules(&dnatChain, config, containerIP)
|
||||
|
||||
ip4t := maybeGetIptables(false)
|
||||
ip6t := maybeGetIptables(true)
|
||||
if ip4t == nil && ip6t == nil {
|
||||
return fmt.Errorf("neither iptables nor ip6tables usable")
|
||||
}
|
||||
|
||||
if ip4t != nil {
|
||||
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return err
|
||||
}
|
||||
if err := dnatChain.check(ip4t); err != nil {
|
||||
return fmt.Errorf("could not check ipv4 dnat: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ip6t != nil {
|
||||
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return err
|
||||
}
|
||||
if err := dnatChain.check(ip6t); err != nil {
|
||||
return fmt.Errorf("could not check ipv6 dnat: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genToplevelDnatChain creates the top-level summary chain that we'll
|
||||
// add our chain to. This is easy, because creating chains is idempotent.
|
||||
// IMPORTANT: do not change this, or else upgrading plugins will require
|
||||
@ -255,6 +295,10 @@ func genMarkMasqChain(markBit int) chain {
|
||||
table: "nat",
|
||||
name: MarkMasqChainName,
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
// Only this entry chain needs to be prepended, because otherwise it is
|
||||
// stomped on by the masquerading rules created by the CNI ptp and bridge
|
||||
// plugins.
|
||||
prependEntry: true,
|
||||
entryRules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd requiring masquerade",
|
||||
|
@ -165,6 +165,12 @@ var _ = Describe("portmap integration tests", func() {
|
||||
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
|
||||
hostIP, hostPort, contIP, containerPort)
|
||||
|
||||
// dump iptables-save output for debugging
|
||||
cmd = exec.Command("iptables-save")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
contOK := testEchoServer(contIP.String(), containerPort, "")
|
||||
|
||||
|
@ -68,7 +68,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
]
|
||||
}
|
||||
}`)
|
||||
c, err := parseConfig(configBytes, "container")
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal("0.3.1"))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
|
||||
@ -91,7 +91,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`)
|
||||
c, err := parseConfig(configBytes, "container")
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal("0.3.1"))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
|
||||
@ -115,7 +115,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
]
|
||||
}
|
||||
}`)
|
||||
_, err := parseConfig(configBytes, "container")
|
||||
_, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).To(MatchError("Invalid host port number: 0"))
|
||||
})
|
||||
|
||||
@ -143,7 +143,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
]
|
||||
}
|
||||
}`)
|
||||
_, err := parseConfig(configBytes, "container")
|
||||
_, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
@ -175,7 +175,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`)
|
||||
|
||||
conf, err := parseConfig(configBytes, "foo")
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
@ -271,7 +271,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`)
|
||||
|
||||
conf, err := parseConfig(configBytes, "foo")
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
@ -323,6 +323,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"--mark", "0x20/0x20",
|
||||
"-j", "MASQUERADE",
|
||||
}},
|
||||
prependEntry: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
@ -22,13 +22,15 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/alexflint/go-filemutex"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const firstTableID = 100
|
||||
@ -370,7 +372,7 @@ RULE_LOOP:
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, bv.BuildString("sbr"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
|
@ -25,23 +25,24 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/vishvananda/netlink"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// TuningConf represents the network tuning configuration.
|
||||
type TuningConf struct {
|
||||
types.NetConf
|
||||
SysCtl map[string]string `json:"sysctl"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult *current.Result `json:"-"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Promisc bool `json:"promisc,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
SysCtl map[string]string `json:"sysctl"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Promisc bool `json:"promisc,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
type MACEnvArgs struct {
|
||||
@ -68,23 +69,6 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if conf.RawPrevResult != nil {
|
||||
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
|
||||
}
|
||||
res, err := version.NewResult(conf.CNIVersion, resultBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
conf.RawPrevResult = nil
|
||||
conf.PrevResult, err = current.NewResultFromResult(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
@ -111,7 +95,15 @@ func changeMacAddr(ifName string, newMacAddr string) error {
|
||||
}
|
||||
|
||||
func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) {
|
||||
for _, i := range config.PrevResult.Interfaces {
|
||||
// Parse previous result.
|
||||
if config.PrevResult == nil {
|
||||
return
|
||||
}
|
||||
|
||||
version.ParsePrevResult(&config.NetConf)
|
||||
result, _ := current.NewResultFromResult(config.PrevResult)
|
||||
|
||||
for _, i := range result.Interfaces {
|
||||
if i.Name == ifName {
|
||||
i.Mac = newMacAddr
|
||||
}
|
||||
@ -144,6 +136,20 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if tuningConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = current.NewResultFromResult(tuningConf.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The directory /proc/sys/net is per network namespace. Enter in the
|
||||
// network namespace before writing on it.
|
||||
|
||||
@ -199,11 +205,80 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tuning"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
tuningConf, err := parseConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if tuningConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
}
|
||||
|
||||
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = current.NewResultFromResult(tuningConf.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
// Check each configured value vs what's currently in the container
|
||||
for key, conf_value := range tuningConf.SysCtl {
|
||||
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
|
||||
fileName = filepath.Clean(fileName)
|
||||
|
||||
contents, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cur_value := strings.TrimSuffix(string(contents), "\n")
|
||||
if conf_value != cur_value {
|
||||
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value)
|
||||
}
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot find container link %v", args.IfName)
|
||||
}
|
||||
|
||||
if tuningConf.Mac != "" {
|
||||
if tuningConf.Mac != link.Attrs().HardwareAddr.String() {
|
||||
return fmt.Errorf("Error: Tuning configured Ethernet of %s is %s, current value is %s",
|
||||
args.IfName, tuningConf.Mac, link.Attrs().HardwareAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.Promisc {
|
||||
if link.Attrs().Promisc == 0 {
|
||||
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
|
||||
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
|
||||
}
|
||||
} else {
|
||||
if link.Attrs().Promisc != 0 {
|
||||
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
|
||||
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.Mtu != 0 {
|
||||
if tuningConf.Mtu != link.Attrs().MTU {
|
||||
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
|
||||
args.IfName, tuningConf.Mtu, link.Attrs().MTU)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,7 +15,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
@ -27,6 +31,49 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *TuningConf, prevResult types.Result) (*TuningConf, []byte, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": name,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
conf := &TuningConf{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, newBytes, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("tuning plugin", func() {
|
||||
var originalNS ns.NetNS
|
||||
const IFNAME string = "dummy0"
|
||||
@ -342,4 +389,296 @@ var _ = Describe("tuning plugin", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures promiscuous mode with CNI 0.4.0 ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.4.0",
|
||||
"promisc": true,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Promisc).To(Equal(1))
|
||||
|
||||
n := &TuningConf{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mtu with CNI 0.4.0 ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.4.0",
|
||||
"mtu": 1454,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().MTU).To(Equal(1454))
|
||||
|
||||
n := &TuningConf{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address (from conf file) with CNI v4.0 ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.4.0",
|
||||
"mac": "c2:11:22:33:44:55",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hw, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
|
||||
n := &TuningConf{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.4.0",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66",
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hw, err := net.ParseMAC("c2:11:22:33:44:66")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
|
||||
n := &TuningConf{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
@ -25,6 +25,8 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// PluginConf is whatever you expect your configuration json to be. This is whatever
|
||||
@ -92,13 +94,22 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove this if this is an "originating" plugin
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("must be called as chained plugin")
|
||||
}
|
||||
|
||||
// Uncomment if this is an "originating" plugin
|
||||
|
||||
//if conf.PrevResult != nil {
|
||||
// return fmt.Errorf("must be called as the first plugin")
|
||||
// }
|
||||
|
||||
// This is some sample code to generate the list of container-side IPs.
|
||||
// We're casting the prevResult to a 0.3.0 response, which can also include
|
||||
// host-side IPs (but doesn't when converted from a 0.2.0 response).
|
||||
//
|
||||
// You don't need this if you are writing an "originating" plugin.
|
||||
containerIPs := make([]net.IP, 0, len(conf.PrevResult.IPs))
|
||||
if conf.CNIVersion != "0.3.0" {
|
||||
for _, ip := range conf.PrevResult.IPs {
|
||||
@ -123,6 +134,8 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("got no container IPs")
|
||||
}
|
||||
|
||||
// Implement your plugin here
|
||||
|
||||
// Pass through the result for the next plugin
|
||||
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
|
||||
}
|
||||
@ -141,11 +154,11 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: implement plugin version
|
||||
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
|
||||
// replace TODO with your plugin name
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("TODO"))
|
||||
}
|
||||
|
||||
func cmdGet(args *skel.CmdArgs) error {
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
Reference in New Issue
Block a user