
add vlan trunk support for veth vlan trunk only support L2 only mode without any IPAM refer ovs-cni design https://github.com/k8snetworkplumbingwg/ovs-cni/blob/main/pkg/plugin/plugin.go design: origin "vlan" option will be PVID or untagged vlan for the network. "vlanTrunk" will setup tagged vlan for veth. entry type: `{ "id": 100 }` will specify only tagged vlan 100 `{ "minID": 100, "maxID": 120 }` will specify tagged vlan from 100 to 120 (include 100 and 120) vlanTrunk is a list of above entry type, so you can use this to add tagged vlan `[ { "id": 100 }, { "minID": 1000, "maxID": 2000 } ]` complete config will be like this { "cniVersion": "0.3.1", "name": "mynet", "type": "bridge", "bridge": "mynet0", "vlan": 100, "vlanTrunk": [ { "id": 101 }, { "minID": 1000, "maxID": 2000 }, { "minID": 3000, "maxID": 4000 } ], "ipam": {} } Signed-off-by: Date Huang <date.huang@suse.com>
2547 lines
71 KiB
Go
2547 lines
71 KiB
Go
// Copyright 2015-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 (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/coreos/go-iptables/iptables"
|
|
"github.com/networkplumbing/go-nft/nft"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netlink/nl"
|
|
|
|
"github.com/containernetworking/cni/pkg/skel"
|
|
"github.com/containernetworking/cni/pkg/types"
|
|
types040 "github.com/containernetworking/cni/pkg/types/040"
|
|
types100 "github.com/containernetworking/cni/pkg/types/100"
|
|
"github.com/containernetworking/plugins/pkg/ip"
|
|
"github.com/containernetworking/plugins/pkg/ns"
|
|
"github.com/containernetworking/plugins/pkg/testutils"
|
|
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
|
)
|
|
|
|
const (
|
|
BRNAME = "bridge0"
|
|
BRNAMEVLAN = "bridge0.100"
|
|
IFNAME = "eth0"
|
|
NAMESERVER = "192.0.2.0"
|
|
)
|
|
|
|
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"`
|
|
// Args *struct {
|
|
// } `json:"runtimeConfig,omitempty"`
|
|
|
|
// A *IPAMArgs `json:"cni"`
|
|
DNS types.DNS `json:"dns"`
|
|
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
|
PrevResult types100.Result `json:"-"`
|
|
}
|
|
|
|
// testCase defines the CNI network configuration and the expected
|
|
// bridge addresses for a test case.
|
|
type testCase struct {
|
|
cniVersion string // CNI Version
|
|
subnet string // Single subnet config: Subnet CIDR
|
|
gateway string // Single subnet config: Gateway
|
|
ranges []rangeInfo // Ranges list (multiple subnets config)
|
|
resolvConf string // host-local resolvConf file path
|
|
isGW bool
|
|
isLayer2 bool
|
|
expGWCIDRs []string // Expected gateway addresses in CIDR form
|
|
vlan int
|
|
vlanTrunk []*VlanTrunk
|
|
removeDefaultVlan bool
|
|
ipMasq bool
|
|
macspoofchk bool
|
|
AddErr020 string
|
|
DelErr020 string
|
|
AddErr010 string
|
|
DelErr010 string
|
|
|
|
envArgs string // CNI_ARGS
|
|
runtimeConfig struct {
|
|
mac string
|
|
}
|
|
args struct {
|
|
cni struct {
|
|
mac string
|
|
}
|
|
}
|
|
|
|
// Unlike the parameters above, the following parameters
|
|
// are expected values to be checked against.
|
|
// e.g. the mac address has several sources: CNI_ARGS, Args and RuntimeConfig.
|
|
expectedMac string
|
|
}
|
|
|
|
// Range definition for each entry in the ranges list
|
|
type rangeInfo struct {
|
|
subnet string
|
|
gateway string
|
|
}
|
|
|
|
// netConf() creates a NetConf structure for a test case.
|
|
func (tc testCase) netConf() *NetConf {
|
|
return &NetConf{
|
|
NetConf: types.NetConf{
|
|
CNIVersion: tc.cniVersion,
|
|
Name: "testConfig",
|
|
Type: "bridge",
|
|
},
|
|
BrName: BRNAME,
|
|
IsGW: tc.isGW,
|
|
IPMasq: false,
|
|
MTU: 5000,
|
|
}
|
|
}
|
|
|
|
// Snippets for generating a JSON network configuration string.
|
|
const (
|
|
netConfStr = `
|
|
"cniVersion": "%s",
|
|
"name": "testConfig",
|
|
"type": "bridge",
|
|
"bridge": "%s"`
|
|
|
|
vlan = `,
|
|
"vlan": %d`
|
|
|
|
vlanTrunkStartStr = `,
|
|
"vlanTrunk": [`
|
|
|
|
vlanTrunk = `
|
|
{
|
|
"id": %d
|
|
}`
|
|
|
|
vlanTrunkRange = `
|
|
{
|
|
"minID": %d,
|
|
"maxID": %d
|
|
}`
|
|
|
|
vlanTrunkEndStr = `
|
|
]`
|
|
|
|
preserveDefaultVlan = `,
|
|
"preserveDefaultVlan": false`
|
|
|
|
netDefault = `,
|
|
"isDefaultGateway": true`
|
|
|
|
ipamStartStr = `,
|
|
"ipam": {
|
|
"type": "host-local"`
|
|
|
|
ipamDataDirStr = `,
|
|
"dataDir": "%s"`
|
|
|
|
ipamResolvConfStr = `,
|
|
"resolvConf": "%s"`
|
|
|
|
ipMasqConfStr = `,
|
|
"ipMasq": %t`
|
|
|
|
// Single subnet configuration (legacy)
|
|
subnetConfStr = `,
|
|
"subnet": "%s"`
|
|
gatewayConfStr = `,
|
|
"gateway": "%s"`
|
|
|
|
// Ranges (multiple subnets) configuration
|
|
rangesStartStr = `,
|
|
"ranges": [`
|
|
rangeSubnetConfStr = `
|
|
[{
|
|
"subnet": "%s"
|
|
}]`
|
|
rangeSubnetGWConfStr = `
|
|
[{
|
|
"subnet": "%s",
|
|
"gateway": "%s"
|
|
}]`
|
|
rangesEndStr = `
|
|
]`
|
|
|
|
ipamEndStr = `
|
|
}`
|
|
|
|
macspoofchkFormat = `,
|
|
"macspoofchk": %t`
|
|
|
|
argsFormat = `,
|
|
"args": {
|
|
"cni": {
|
|
"mac": %q
|
|
}
|
|
}`
|
|
|
|
runtimeConfig = `,
|
|
"RuntimeConfig": {
|
|
"mac": %q
|
|
}`
|
|
)
|
|
|
|
// netConfJSON() generates a JSON network configuration string
|
|
// 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.removeDefaultVlan {
|
|
conf += preserveDefaultVlan
|
|
}
|
|
}
|
|
|
|
if tc.isLayer2 && tc.vlanTrunk != nil {
|
|
conf += vlanTrunkStartStr
|
|
for i, vlan := range tc.vlanTrunk {
|
|
if i > 0 {
|
|
conf += ","
|
|
}
|
|
if vlan.ID != nil {
|
|
conf += fmt.Sprintf(vlanTrunk, *vlan.ID)
|
|
}
|
|
if vlan.MinID != nil && vlan.MaxID != nil {
|
|
conf += fmt.Sprintf(vlanTrunkRange, *vlan.MinID, *vlan.MaxID)
|
|
}
|
|
}
|
|
conf += vlanTrunkEndStr
|
|
}
|
|
|
|
if tc.ipMasq {
|
|
conf += tc.ipMasqConfig()
|
|
}
|
|
if tc.args.cni.mac != "" {
|
|
conf += fmt.Sprintf(argsFormat, tc.args.cni.mac)
|
|
}
|
|
if tc.runtimeConfig.mac != "" {
|
|
conf += fmt.Sprintf(runtimeConfig, tc.runtimeConfig.mac)
|
|
}
|
|
if tc.macspoofchk {
|
|
conf += fmt.Sprintf(macspoofchkFormat, tc.macspoofchk)
|
|
}
|
|
|
|
if !tc.isLayer2 {
|
|
conf += netDefault
|
|
if tc.subnet != "" || tc.ranges != nil {
|
|
conf += ipamStartStr
|
|
if dataDir != "" {
|
|
conf += fmt.Sprintf(ipamDataDirStr, dataDir)
|
|
}
|
|
if tc.subnet != "" {
|
|
conf += tc.subnetConfig()
|
|
}
|
|
if tc.ranges != nil {
|
|
conf += tc.rangesConfig()
|
|
}
|
|
if tc.resolvConf != "" {
|
|
conf += tc.resolvConfConfig()
|
|
}
|
|
conf += ipamEndStr
|
|
}
|
|
} else {
|
|
conf += `,
|
|
"ipam": {}`
|
|
}
|
|
return "{" + conf + "\n}"
|
|
}
|
|
|
|
func (tc testCase) subnetConfig() string {
|
|
conf := fmt.Sprintf(subnetConfStr, tc.subnet)
|
|
if tc.gateway != "" {
|
|
conf += fmt.Sprintf(gatewayConfStr, tc.gateway)
|
|
}
|
|
return conf
|
|
}
|
|
|
|
func (tc testCase) ipMasqConfig() string {
|
|
conf := fmt.Sprintf(ipMasqConfStr, tc.ipMasq)
|
|
return conf
|
|
}
|
|
|
|
func (tc testCase) rangesConfig() string {
|
|
conf := rangesStartStr
|
|
for i, tcRange := range tc.ranges {
|
|
if i > 0 {
|
|
conf += ","
|
|
}
|
|
if tcRange.gateway != "" {
|
|
conf += fmt.Sprintf(rangeSubnetGWConfStr, tcRange.subnet, tcRange.gateway)
|
|
} else {
|
|
conf += fmt.Sprintf(rangeSubnetConfStr, tcRange.subnet)
|
|
}
|
|
}
|
|
return conf + rangesEndStr
|
|
}
|
|
|
|
func (tc testCase) resolvConfConfig() string {
|
|
conf := fmt.Sprintf(ipamResolvConfStr, tc.resolvConf)
|
|
return conf
|
|
}
|
|
|
|
func newResolvConf() (string, error) {
|
|
f, err := os.CreateTemp("", "host_local_resolv")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
name := f.Name()
|
|
_, err = f.WriteString(fmt.Sprintf("nameserver %s", NAMESERVER))
|
|
return name, err
|
|
}
|
|
|
|
func deleteResolvConf(path string) error {
|
|
return os.Remove(path)
|
|
}
|
|
|
|
var counter uint
|
|
|
|
// createCmdArgs generates network configuration and creates command
|
|
// arguments for a test case.
|
|
func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs {
|
|
conf := tc.netConfJSON(dataDir)
|
|
// defer func() { counter += 1 }()
|
|
return &skel.CmdArgs{
|
|
ContainerID: fmt.Sprintf("dummy-%d", counter),
|
|
Netns: targetNS.Path(),
|
|
IfName: IFNAME,
|
|
StdinData: []byte(conf),
|
|
Args: tc.envArgs,
|
|
}
|
|
}
|
|
|
|
// createCheckCmdArgs generates network configuration and creates command
|
|
// arguments for a Check test case.
|
|
func (tc testCase) createCheckCmdArgs(targetNS ns.NetNS, config *Net) *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(),
|
|
IfName: IFNAME,
|
|
StdinData: conf,
|
|
}
|
|
}
|
|
|
|
// expectedCIDRs determines the IPv4 and IPv6 CIDRs in which the resulting
|
|
// addresses are expected to be contained.
|
|
func (tc testCase) expectedCIDRs() ([]*net.IPNet, []*net.IPNet) {
|
|
var cidrsV4, cidrsV6 []*net.IPNet
|
|
appendSubnet := func(subnet string) {
|
|
ip, cidr, err := net.ParseCIDR(subnet)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
if ipVersion(ip) == "4" {
|
|
cidrsV4 = append(cidrsV4, cidr)
|
|
} else {
|
|
cidrsV6 = append(cidrsV6, cidr)
|
|
}
|
|
}
|
|
if tc.subnet != "" {
|
|
appendSubnet(tc.subnet)
|
|
}
|
|
for _, r := range tc.ranges {
|
|
appendSubnet(r.subnet)
|
|
}
|
|
return cidrsV4, cidrsV6
|
|
}
|
|
|
|
// delBridgeAddrs() deletes addresses from the bridge
|
|
func delBridgeAddrs(testNS ns.NetNS) {
|
|
err := testNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
br, err := netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
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())
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
func ipVersion(ip net.IP) string {
|
|
if ip.To4() != nil {
|
|
return "4"
|
|
}
|
|
return "6"
|
|
}
|
|
|
|
func countIPAMIPs(path string) (int, error) {
|
|
count := 0
|
|
entries, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
if net.ParseIP(entry.Name()) != nil {
|
|
count++
|
|
}
|
|
}
|
|
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 {
|
|
cmdAddTest(tc testCase, dataDir string) (types.Result, error)
|
|
cmdCheckTest(tc testCase, conf *Net, dataDir string)
|
|
cmdDelTest(tc testCase, dataDir string)
|
|
}
|
|
|
|
type testerBase struct {
|
|
testNS ns.NetNS
|
|
targetNS ns.NetNS
|
|
args *skel.CmdArgs
|
|
vethName string
|
|
}
|
|
|
|
type (
|
|
testerV10x testerBase
|
|
testerV04x testerBase
|
|
testerV03x testerBase
|
|
testerV01xOr02x testerBase
|
|
)
|
|
|
|
func newTesterByVersion(version string, testNS, targetNS ns.NetNS) cmdAddDelTester {
|
|
switch {
|
|
case strings.HasPrefix(version, "1.0."):
|
|
return &testerV10x{
|
|
testNS: testNS,
|
|
targetNS: targetNS,
|
|
}
|
|
case strings.HasPrefix(version, "0.4."):
|
|
return &testerV04x{
|
|
testNS: testNS,
|
|
targetNS: targetNS,
|
|
}
|
|
case strings.HasPrefix(version, "0.3."):
|
|
return &testerV03x{
|
|
testNS: testNS,
|
|
targetNS: targetNS,
|
|
}
|
|
default:
|
|
return &testerV01xOr02x{
|
|
testNS: testNS,
|
|
targetNS: targetNS,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
|
|
// Generate network config and command arguments
|
|
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
|
|
|
// Execute cmdADD on the plugin
|
|
var result *types100.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))
|
|
|
|
resultType, err := r.GetAsVersion(tc.cniVersion)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
result = resultType.(*types100.Result)
|
|
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(result.Interfaces).To(HaveLen(4))
|
|
} else {
|
|
Expect(result.Interfaces).To(HaveLen(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))
|
|
if tc.expectedMac != "" {
|
|
Expect(result.Interfaces[2].Mac).To(Equal(tc.expectedMac))
|
|
}
|
|
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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// Check the bridge vlan filtering equals true
|
|
if tc.vlan != 0 || tc.vlanTrunk != nil {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
|
|
} else {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
|
|
}
|
|
|
|
// 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(addrs).ToNot(BeEmpty())
|
|
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(BeTrue(), fmt.Sprintf("failed to find %s", cidr))
|
|
}
|
|
|
|
// Check for the veth link in the main namespace
|
|
links, err := netlink.LinkList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(links).To(HaveLen(5)) // Bridge, Bridge vlan veth, veth, and loopback
|
|
} else {
|
|
Expect(links).To(HaveLen(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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// check VlanTrunks exist on the veth interface
|
|
if tc.vlanTrunk != nil {
|
|
interfaceMap, err := netlink.BridgeVlanList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
|
|
Expect(isExist).To(BeTrue())
|
|
|
|
for _, vlanEntry := range tc.vlanTrunk {
|
|
if vlanEntry.ID != nil {
|
|
Expect(checkVlan(*vlanEntry.ID, vlans)).To(BeTrue())
|
|
}
|
|
if vlanEntry.MinID != nil && vlanEntry.MaxID != nil {
|
|
for vid := *vlanEntry.MinID; vid <= *vlanEntry.MaxID; vid++ {
|
|
Expect(checkVlan(vid, 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))
|
|
}
|
|
|
|
// Check that resolvConf was used properly
|
|
if !tc.isLayer2 && tc.resolvConf != "" {
|
|
Expect(result.DNS.Nameservers).To(Equal([]string{NAMESERVER}))
|
|
}
|
|
|
|
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(addrs).To(HaveLen(len(expCIDRsV4)))
|
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
|
Expect(addrs).To(HaveLen(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(BeTrue())
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (tester *testerV10x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
|
// Generate network config and command arguments
|
|
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf)
|
|
|
|
// 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
|
|
})
|
|
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(addrs).To(HaveLen(len(expCIDRsV4)))
|
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
|
Expect(addrs).To(HaveLen(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(BeTrue())
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
func (tester *testerV10x) 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())
|
|
}
|
|
|
|
func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
|
|
// Generate network config and command arguments
|
|
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
|
|
|
// Execute cmdADD on the plugin
|
|
var result *types040.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))
|
|
|
|
resultType, err := r.GetAsVersion(tc.cniVersion)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
result = resultType.(*types040.Result)
|
|
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(result.Interfaces).To(HaveLen(4))
|
|
} else {
|
|
Expect(result.Interfaces).To(HaveLen(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))
|
|
if tc.expectedMac != "" {
|
|
Expect(result.Interfaces[2].Mac).To(Equal(tc.expectedMac))
|
|
}
|
|
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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// Check the bridge vlan filtering equals true
|
|
if tc.vlan != 0 || tc.vlanTrunk != nil {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
|
|
} else {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
|
|
}
|
|
|
|
// 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(addrs).ToNot(BeEmpty())
|
|
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(BeTrue())
|
|
}
|
|
|
|
// Check for the veth link in the main namespace
|
|
links, err := netlink.LinkList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(links).To(HaveLen(5)) // Bridge, Bridge vlan veth, veth, and loopback
|
|
} else {
|
|
Expect(links).To(HaveLen(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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// check VlanTrunks exist on the veth interface
|
|
if tc.vlanTrunk != nil {
|
|
interfaceMap, err := netlink.BridgeVlanList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
|
|
Expect(isExist).To(BeTrue())
|
|
|
|
for _, vlanEntry := range tc.vlanTrunk {
|
|
if vlanEntry.ID != nil {
|
|
Expect(checkVlan(*vlanEntry.ID, vlans)).To(BeTrue())
|
|
}
|
|
if vlanEntry.MinID != nil && vlanEntry.MaxID != nil {
|
|
for vid := *vlanEntry.MinID; vid <= *vlanEntry.MaxID; vid++ {
|
|
Expect(checkVlan(vid, 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(addrs).To(HaveLen(len(expCIDRsV4)))
|
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
|
Expect(addrs).To(HaveLen(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(BeTrue())
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
|
// Generate network config and command arguments
|
|
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf)
|
|
|
|
// 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
|
|
})
|
|
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(addrs).To(HaveLen(len(expCIDRsV4)))
|
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
|
Expect(addrs).To(HaveLen(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(BeTrue())
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
|
|
// Generate network config and command arguments
|
|
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
|
|
|
// Execute cmdADD on the plugin
|
|
var result *types040.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))
|
|
|
|
resultType, err := r.GetAsVersion(tc.cniVersion)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
result = resultType.(*types040.Result)
|
|
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(result.Interfaces).To(HaveLen(4))
|
|
} else {
|
|
Expect(result.Interfaces).To(HaveLen(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))
|
|
if tc.expectedMac != "" {
|
|
Expect(result.Interfaces[2].Mac).To(Equal(tc.expectedMac))
|
|
}
|
|
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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// Check the bridge vlan filtering equals true
|
|
if tc.vlan != 0 || tc.vlanTrunk != nil {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
|
|
} else {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
|
|
}
|
|
|
|
// 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(addrs).ToNot(BeEmpty())
|
|
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(BeTrue())
|
|
}
|
|
|
|
// Check for the veth link in the main namespace
|
|
links, err := netlink.LinkList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(links).To(HaveLen(5)) // Bridge, Bridge vlan veth, veth, and loopback
|
|
} else {
|
|
Expect(links).To(HaveLen(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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// check VlanTrunks exist on the veth interface
|
|
if tc.vlanTrunk != nil {
|
|
interfaceMap, err := netlink.BridgeVlanList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
|
|
Expect(isExist).To(BeTrue())
|
|
|
|
for _, vlanEntry := range tc.vlanTrunk {
|
|
if vlanEntry.ID != nil {
|
|
Expect(checkVlan(*vlanEntry.ID, vlans)).To(BeTrue())
|
|
}
|
|
if vlanEntry.MinID != nil && vlanEntry.MaxID != nil {
|
|
for vid := *vlanEntry.MinID; vid <= *vlanEntry.MaxID; vid++ {
|
|
Expect(checkVlan(vid, 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(addrs).To(HaveLen(len(expCIDRsV4)))
|
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
|
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(BeTrue())
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return result, nil
|
|
}
|
|
|
|
func (tester *testerV03x) cmdCheckTest(_ testCase, _ *Net, _ string) {
|
|
}
|
|
|
|
func (tester *testerV03x) cmdDelTest(_ testCase, _ string) {
|
|
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())
|
|
}
|
|
|
|
func expect020AddError(tc testCase) bool {
|
|
return tc.AddErr020 != "" && tc.cniVersion == "0.2.0"
|
|
}
|
|
|
|
func expect020DelError(tc testCase) bool {
|
|
return tc.DelErr020 != "" && tc.cniVersion == "0.2.0"
|
|
}
|
|
|
|
func expect010AddError(tc testCase) bool {
|
|
return tc.AddErr010 != "" && tc.cniVersion == "0.1.0"
|
|
}
|
|
|
|
func expect010DelError(tc testCase) bool {
|
|
return tc.DelErr010 != "" && tc.cniVersion == "0.1.0"
|
|
}
|
|
|
|
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
|
|
// Generate network config and command arguments
|
|
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
|
|
|
|
var hostNSVlanMap map[int32][]*nl.BridgeVlanInfo
|
|
|
|
// Execute cmdADD on the plugin
|
|
err := tester.testNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
|
|
return cmdAdd(tester.args)
|
|
})
|
|
|
|
if expect020AddError(tc) || expect010AddError(tc) {
|
|
return err
|
|
}
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(strings.Index(string(raw), "\"ip\":")).Should(BeNumerically(">", 0))
|
|
|
|
// We expect a version 0.1.0 or 0.2.0 result
|
|
_, err = r.GetAsVersion(tc.cniVersion)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Make sure bridge link exists
|
|
link, err := netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
|
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
|
|
|
|
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())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
// Check the bridge vlan filtering equals true
|
|
if tc.vlan != 0 {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeTrue())
|
|
} else {
|
|
Expect(*link.(*netlink.Bridge).VlanFiltering).To(BeFalse())
|
|
}
|
|
|
|
// 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(addrs).ToNot(BeEmpty())
|
|
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(BeTrue())
|
|
}
|
|
|
|
// Check for the veth link in the main namespace; can't
|
|
// check the for the specific link since version 0.1.0
|
|
// doesn't report interfaces
|
|
links, err := netlink.LinkList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
if !tc.isLayer2 && tc.vlan != 0 {
|
|
Expect(links).To(HaveLen(5)) // Bridge, Bridge vlan veth, veth, and loopback
|
|
} else {
|
|
Expect(links).To(HaveLen(3)) // Bridge, veth, and loopback
|
|
}
|
|
|
|
// Grab the vlan map in the host NS for checking later
|
|
if tc.vlan != 0 {
|
|
hostNSVlanMap, err = netlink.BridgeVlanList()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
return nil
|
|
})
|
|
if expect020AddError(tc) {
|
|
Expect(err).To(MatchError(tc.AddErr020))
|
|
return nil, nil
|
|
} else if expect010AddError(tc) {
|
|
Expect(err).To(MatchError(tc.AddErr010))
|
|
return nil, 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(addrs).To(HaveLen(len(expCIDRsV4)))
|
|
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
|
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(BeTrue())
|
|
}
|
|
|
|
// Validate VLAN in the host NS. Since 0.1.0/0.2.0 don't return
|
|
// any host interface information, we have to look up the container
|
|
// namespace veth's peer index instead
|
|
if tc.vlan != 0 {
|
|
_, peerIndex, err := ip.GetVethPeerIfindex(IFNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(peerIndex).To(BeNumerically(">", 0))
|
|
vlans, isExist := hostNSVlanMap[int32(peerIndex)]
|
|
Expect(isExist).To(BeTrue())
|
|
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
|
|
if tc.removeDefaultVlan {
|
|
Expect(vlans).To(HaveLen(1))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return nil, nil
|
|
}
|
|
|
|
func (tester *testerV01xOr02x) cmdCheckTest(_ testCase, _ *Net, _ string) {
|
|
}
|
|
|
|
func (tester *testerV01xOr02x) cmdDelTest(tc testCase, _ string) {
|
|
err := tester.testNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
err := testutils.CmdDelWithArgs(tester.args, func() error {
|
|
return cmdDel(tester.args)
|
|
})
|
|
switch {
|
|
case expect020DelError(tc):
|
|
Expect(err).To(MatchError(tc.DelErr020))
|
|
case expect010DelError(tc):
|
|
Expect(err).To(MatchError(tc.DelErr010))
|
|
default:
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Make sure the container veth has been deleted; cannot check
|
|
// host veth as version 0.1.0 can't report its name
|
|
err = tester.testNS.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())
|
|
}
|
|
|
|
func cmdAddDelTest(origNS, targetNS ns.NetNS, tc testCase, dataDir string) {
|
|
tester := newTesterByVersion(tc.cniVersion, origNS, targetNS)
|
|
|
|
// Test IP allocation
|
|
_, err := tester.cmdAddTest(tc, dataDir)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Test IP Release
|
|
tester.cmdDelTest(tc, dataDir)
|
|
|
|
// Clean up bridge addresses for next test case
|
|
delBridgeAddrs(origNS)
|
|
}
|
|
|
|
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(origNS, targetNS ns.NetNS, tc testCase, dataDir string) {
|
|
tester := newTesterByVersion(tc.cniVersion, origNS, 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(origNS)
|
|
|
|
if tc.vlan != 0 && !tc.isLayer2 {
|
|
delVlanAddrs(origNS, tc.vlan)
|
|
}
|
|
}
|
|
|
|
var _ = Describe("bridge Operations", func() {
|
|
var originalNS, targetNS ns.NetNS
|
|
var dataDir string
|
|
|
|
BeforeEach(func() {
|
|
// Create a new NetNS so we don't modify the host
|
|
var err error
|
|
originalNS, err = testutils.NewNS()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
targetNS, err = testutils.NewNS()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
dataDir, err = os.MkdirTemp("", "bridge_test")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Do not emulate an error, each test will set this if needed
|
|
debugPostIPAMError = nil
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
|
Expect(originalNS.Close()).To(Succeed())
|
|
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
|
Expect(targetNS.Close()).To(Succeed())
|
|
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
|
|
})
|
|
|
|
var (
|
|
correctID int = 10
|
|
correctMinID int = 100
|
|
correctMaxID int = 105
|
|
incorrectMinID int = 1000
|
|
incorrectMaxID int = 100
|
|
overID int = 5000
|
|
negativeID int = -1
|
|
)
|
|
|
|
DescribeTable(
|
|
"collectVlanTrunk succeeds",
|
|
func(vlanTrunks []*VlanTrunk, expectedVIDs []int) {
|
|
Expect(collectVlanTrunk(vlanTrunks)).To(ConsistOf(expectedVIDs))
|
|
},
|
|
Entry("when provided an empty VLAN trunk configuration", []*VlanTrunk{}, nil),
|
|
Entry("when provided a VLAN trunk configuration with both min / max range", []*VlanTrunk{
|
|
{
|
|
MinID: &correctMinID,
|
|
MaxID: &correctMaxID,
|
|
},
|
|
}, []int{100, 101, 102, 103, 104, 105}),
|
|
Entry("when provided a VLAN trunk configuration with id only", []*VlanTrunk{
|
|
{
|
|
ID: &correctID,
|
|
},
|
|
}, []int{10}),
|
|
Entry("when provided a VLAN trunk configuration with id and range", []*VlanTrunk{
|
|
{
|
|
ID: &correctID,
|
|
},
|
|
{
|
|
MinID: &correctMinID,
|
|
MaxID: &correctMaxID,
|
|
},
|
|
}, []int{10, 100, 101, 102, 103, 104, 105}),
|
|
)
|
|
|
|
DescribeTable(
|
|
"collectVlanTrunk failed",
|
|
func(vlanTrunks []*VlanTrunk, expectedError error) {
|
|
_, err := collectVlanTrunk(vlanTrunks)
|
|
Expect(err).To(MatchError(expectedError))
|
|
},
|
|
Entry("when not passed the maxID", []*VlanTrunk{{MinID: &correctMinID}}, fmt.Errorf("minID and maxID should be configured simultaneously, maxID is missing")),
|
|
Entry("when not passed the minID", []*VlanTrunk{{MaxID: &correctMaxID}}, fmt.Errorf("minID and maxID should be configured simultaneously, minID is missing")),
|
|
Entry("when the minID is negative", []*VlanTrunk{{MinID: &negativeID, MaxID: &correctMaxID}}, fmt.Errorf("incorrect trunk minID parameter")),
|
|
Entry("when the minID is larger than 4094", []*VlanTrunk{{MinID: &overID, MaxID: &correctMaxID}}, fmt.Errorf("incorrect trunk minID parameter")),
|
|
Entry("when the maxID is larger than 4094", []*VlanTrunk{{MinID: &correctMinID, MaxID: &overID}}, fmt.Errorf("incorrect trunk maxID parameter")),
|
|
Entry("when the maxID is negative", []*VlanTrunk{{MinID: &correctMinID, MaxID: &overID}}, fmt.Errorf("incorrect trunk maxID parameter")),
|
|
Entry("when the ID is larger than 4094", []*VlanTrunk{{ID: &overID}}, fmt.Errorf("incorrect trunk id parameter")),
|
|
Entry("when the ID is negative", []*VlanTrunk{{ID: &negativeID}}, fmt.Errorf("incorrect trunk id parameter")),
|
|
Entry("when the maxID is smaller than minID", []*VlanTrunk{{MinID: &incorrectMinID, MaxID: &incorrectMaxID}}, fmt.Errorf("minID is greater than maxID in trunk parameter")),
|
|
)
|
|
|
|
for _, ver := range testutils.AllSpecVersions {
|
|
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
|
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
|
ver := ver
|
|
|
|
It(fmt.Sprintf("[%s] creates a bridge", ver), func() {
|
|
conf := testCase{cniVersion: ver}.netConf()
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
bridge, _, err := setupBridge(conf)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(bridge.Attrs().Name).To(Equal(BRNAME))
|
|
|
|
// Double check that the link was added
|
|
link, err := netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
|
Expect(link.Attrs().Promisc).To(Equal(0))
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It(fmt.Sprintf("[%s] handles an existing bridge", ver), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
err := netlink.LinkAdd(&netlink.Bridge{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: BRNAME,
|
|
},
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
link, err := netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
|
ifindex := link.Attrs().Index
|
|
|
|
tc := testCase{cniVersion: ver, isGW: false}
|
|
conf := tc.netConf()
|
|
|
|
bridge, _, err := setupBridge(conf)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(bridge.Attrs().Name).To(Equal(BRNAME))
|
|
Expect(bridge.Attrs().Index).To(Equal(ifindex))
|
|
|
|
// Double check that the link has the same ifindex
|
|
link, err = netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(link.Attrs().Name).To(Equal(BRNAME))
|
|
Expect(link.Attrs().Index).To(Equal(ifindex))
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
for i, tc := range []testCase{
|
|
{
|
|
// IPv4 only
|
|
subnet: "10.1.2.0/24",
|
|
expGWCIDRs: []string{"10.1.2.1/24"},
|
|
},
|
|
{
|
|
// IPv6 only
|
|
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",
|
|
},
|
|
},
|
|
{
|
|
// 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",
|
|
},
|
|
AddErr020: "CNI version 0.2.0 does not support more than 1 address per family",
|
|
DelErr020: "CNI version 0.2.0 does not support more than 1 address per family",
|
|
AddErr010: "CNI version 0.1.0 does not support more than 1 address per family",
|
|
DelErr010: "CNI version 0.1.0 does not support more than 1 address per family",
|
|
},
|
|
{
|
|
// with resolvConf DNS settings
|
|
subnet: "10.1.2.0/24",
|
|
expGWCIDRs: []string{"10.1.2.1/24"},
|
|
resolvConf: resolvConf,
|
|
},
|
|
} {
|
|
tc := tc
|
|
i := i
|
|
It(fmt.Sprintf("[%s] (%d) configures and deconfigures a bridge and veth with default route with ADD/DEL", ver, i), func() {
|
|
tc.cniVersion = ver
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
}
|
|
|
|
It(fmt.Sprintf("[%s] configures and deconfigures a l2 bridge and veth with ADD/DEL", ver), func() {
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
isLayer2: true,
|
|
AddErr020: "cannot convert: no valid IP addresses",
|
|
AddErr010: "cannot convert: no valid IP addresses",
|
|
}
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
|
|
It(fmt.Sprintf("[%s] configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL", ver), func() {
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
isLayer2: true,
|
|
vlan: 100,
|
|
AddErr020: "cannot convert: no valid IP addresses",
|
|
AddErr010: "cannot convert: no valid IP addresses",
|
|
}
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
|
|
// TODO find some way to put pointer
|
|
It(fmt.Sprintf("[%s] configures and deconfigures a l2 bridge with vlan id 100, vlanTrunk 101,200~210 using ADD/DEL", ver), func() {
|
|
id, minID, maxID := 101, 200, 210
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
isLayer2: true,
|
|
vlanTrunk: []*VlanTrunk{
|
|
{ID: &id},
|
|
{
|
|
MinID: &minID,
|
|
MaxID: &maxID,
|
|
},
|
|
},
|
|
AddErr020: "cannot convert: no valid IP addresses",
|
|
AddErr010: "cannot convert: no valid IP addresses",
|
|
}
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
|
|
It(fmt.Sprintf("[%s] configures and deconfigures a l2 bridge with vlan id 100 and no default vlan using ADD/DEL", ver), func() {
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
isLayer2: true,
|
|
vlan: 100,
|
|
removeDefaultVlan: true,
|
|
AddErr020: "cannot convert: no valid IP addresses",
|
|
AddErr010: "cannot convert: no valid IP addresses",
|
|
}
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
|
|
for i, tc := range []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,
|
|
AddErr020: "CNI version 0.2.0 does not support more than 1 address per family",
|
|
DelErr020: "CNI version 0.2.0 does not support more than 1 address per family",
|
|
AddErr010: "CNI version 0.1.0 does not support more than 1 address per family",
|
|
DelErr010: "CNI version 0.1.0 does not support more than 1 address per family",
|
|
},
|
|
} {
|
|
tc := tc
|
|
i := i
|
|
It(fmt.Sprintf("[%s] (%d) configures and deconfigures a bridge, veth with default route and vlanID 100 with ADD/DEL", ver, i), func() {
|
|
tc.cniVersion = ver
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
It(fmt.Sprintf("[%s] (%d) configures and deconfigures a bridge, veth with default route and vlanID 100 and no default vlan with ADD/DEL", ver, i), func() {
|
|
tc.cniVersion = ver
|
|
tc.removeDefaultVlan = true
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
}
|
|
|
|
for i, tc := range []testCase{
|
|
{
|
|
// IPv4 only
|
|
subnet: "10.1.2.0/24",
|
|
expGWCIDRs: []string{"10.1.2.1/24"},
|
|
},
|
|
{
|
|
// IPv6 only
|
|
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",
|
|
},
|
|
},
|
|
} {
|
|
tc := tc
|
|
i := i
|
|
It(fmt.Sprintf("[%s] (%d) configures and deconfigures a bridge and veth with default route with ADD/DEL", ver, i), func() {
|
|
tc.cniVersion = ver
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
}
|
|
|
|
It("deconfigures an unconfigured bridge with DEL", func() {
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
subnet: "10.1.2.0/24",
|
|
expGWCIDRs: []string{"10.1.2.1/24"},
|
|
}
|
|
|
|
tester := testerV03x{
|
|
testNS: originalNS,
|
|
targetNS: targetNS,
|
|
args: tc.createCmdArgs(targetNS, dataDir),
|
|
}
|
|
|
|
// Execute cmdDEL on the plugin, expect no errors
|
|
tester.cmdDelTest(tc, dataDir)
|
|
})
|
|
|
|
for i, tc := range []struct {
|
|
gwCIDRFirst string
|
|
gwCIDRSecond string
|
|
}{
|
|
{
|
|
// IPv4
|
|
gwCIDRFirst: "10.0.0.1/8",
|
|
gwCIDRSecond: "10.1.2.3/16",
|
|
},
|
|
{
|
|
// IPv6, overlapping subnets
|
|
gwCIDRFirst: "2001:db8:1::1/48",
|
|
gwCIDRSecond: "2001:db8:1:2::1/64",
|
|
},
|
|
{
|
|
// IPv6, non-overlapping subnets
|
|
gwCIDRFirst: "2001:db8:1:2::1/64",
|
|
gwCIDRSecond: "fd00:1234::1/64",
|
|
},
|
|
} {
|
|
tc := tc
|
|
i := i
|
|
It(fmt.Sprintf("[%s] (%d) ensure bridge address", ver, i), func() {
|
|
conf := testCase{cniVersion: ver, isGW: true}.netConf()
|
|
|
|
gwIP, gwSubnet, err := net.ParseCIDR(tc.gwCIDRFirst)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
gwnFirst := net.IPNet{IP: gwIP, Mask: gwSubnet.Mask}
|
|
gwIP, gwSubnet, err = net.ParseCIDR(tc.gwCIDRSecond)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
gwnSecond := net.IPNet{IP: gwIP, Mask: gwSubnet.Mask}
|
|
|
|
var family, expNumAddrs int
|
|
switch {
|
|
case gwIP.To4() != nil:
|
|
family = netlink.FAMILY_V4
|
|
expNumAddrs = 1
|
|
default:
|
|
family = netlink.FAMILY_V6
|
|
// Expect configured gw address plus link local
|
|
expNumAddrs = 2
|
|
}
|
|
|
|
subnetsOverlap := gwnFirst.Contains(gwnSecond.IP) ||
|
|
gwnSecond.Contains(gwnFirst.IP)
|
|
|
|
err = originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
// Create the bridge
|
|
bridge, _, err := setupBridge(conf)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Function to check IP address(es) on bridge
|
|
checkBridgeIPs := func(cidr0, cidr1 string) {
|
|
addrs, err := netlink.AddrList(bridge, family)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(addrs).To(HaveLen(expNumAddrs))
|
|
addr := addrs[0].IPNet.String()
|
|
Expect(addr).To(Equal(cidr0))
|
|
if cidr1 != "" {
|
|
addr = addrs[1].IPNet.String()
|
|
Expect(addr).To(Equal(cidr1))
|
|
}
|
|
}
|
|
|
|
// Check if ForceAddress has default value
|
|
Expect(conf.ForceAddress).To(BeFalse())
|
|
|
|
// Set first address on bridge
|
|
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 = 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
|
|
Expect(err).To(HaveOccurred())
|
|
checkBridgeIPs(tc.gwCIDRFirst, "")
|
|
} else {
|
|
// Non-overlapping IPv6 subnets:
|
|
// There should be 2 addresses (in addition to link local)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
expNumAddrs++
|
|
checkBridgeIPs(tc.gwCIDRSecond, tc.gwCIDRFirst)
|
|
}
|
|
|
|
// Set the second address on the bridge
|
|
// with ForceAddress set to true.
|
|
err = ensureAddr(bridge, family, &gwnSecond, true)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
if family == netlink.FAMILY_V4 || subnetsOverlap {
|
|
// IPv4 or overlapping IPv6 subnets:
|
|
// IP address should be reconfigured.
|
|
checkBridgeIPs(tc.gwCIDRSecond, "")
|
|
} else {
|
|
// Non-overlapping IPv6 subnets:
|
|
// There should be 2 addresses (in addition to link local)
|
|
checkBridgeIPs(tc.gwCIDRSecond, tc.gwCIDRFirst)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Clean up bridge addresses for next test case
|
|
delBridgeAddrs(originalNS)
|
|
})
|
|
}
|
|
|
|
It(fmt.Sprintf("[%s] ensure promiscuous mode on bridge", ver), func() {
|
|
const IFNAME = "bridge0"
|
|
|
|
conf := &NetConf{
|
|
NetConf: types.NetConf{
|
|
Name: "testConfig",
|
|
Type: "bridge",
|
|
},
|
|
BrName: IFNAME,
|
|
IsGW: true,
|
|
IPMasq: false,
|
|
HairpinMode: false,
|
|
PromiscMode: true,
|
|
MTU: 5000,
|
|
}
|
|
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
conf.NetConf.CNIVersion = ver
|
|
_, _, err := setupBridge(conf)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
// Check if ForceAddress has default value
|
|
Expect(conf.ForceAddress).To(BeFalse())
|
|
|
|
// Check if promiscuous mode is set correctly
|
|
link, err := netlink.LinkByName("bridge0")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(link.Attrs().Promisc).To(Equal(1))
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
for i, tc := range []testCase{
|
|
{
|
|
subnet: "10.1.2.0/24",
|
|
},
|
|
{
|
|
subnet: "2001:db8:42::/64",
|
|
},
|
|
} {
|
|
tc := tc
|
|
i := i
|
|
It(fmt.Sprintf("[%s] (%d) creates a bridge with a stable MAC addresses", ver, i), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
tc.cniVersion = ver
|
|
_, _, err := setupBridge(tc.netConf())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
link, err := netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
origMac := link.Attrs().HardwareAddr
|
|
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
|
|
link, err = netlink.LinkByName(BRNAME)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(link.Attrs().HardwareAddr).To(Equal(origMac))
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
}
|
|
|
|
It(fmt.Sprintf("[%s] uses an explicit MAC addresses for the container iface (from CNI_ARGS)", ver), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
const expectedMac = "02:00:00:00:00:00"
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
subnet: "10.1.2.0/24",
|
|
envArgs: "MAC=" + expectedMac,
|
|
|
|
expectedMac: expectedMac,
|
|
}
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It(fmt.Sprintf("[%s] uses an explicit MAC addresses for the container iface (from Args)", ver), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
const expectedMac = "02:00:00:00:00:00"
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
subnet: "10.1.2.0/24",
|
|
envArgs: "MAC=" + "02:00:00:00:04:56",
|
|
|
|
expectedMac: expectedMac,
|
|
}
|
|
tc.args.cni.mac = expectedMac
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It(fmt.Sprintf("[%s] uses an explicit MAC addresses for the container iface (from RuntimeConfig)", ver), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
const expectedMac = "02:00:00:00:00:00"
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
subnet: "10.1.2.0/24",
|
|
envArgs: "MAC=" + "02:00:00:00:04:56",
|
|
|
|
expectedMac: expectedMac,
|
|
}
|
|
tc.args.cni.mac = "02:00:00:00:07:89"
|
|
tc.runtimeConfig.mac = expectedMac
|
|
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
|
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It(fmt.Sprintf("[%s] checks ip release in case of error", ver), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
subnet: "10.1.2.0/24",
|
|
}
|
|
|
|
_, _, err := setupBridge(tc.netConf())
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
args := tc.createCmdArgs(originalNS, dataDir)
|
|
|
|
// get number of allocated IPs before asking for a new one
|
|
before, err := countIPAMIPs(dataDir)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
debugPostIPAMError = fmt.Errorf("debugPostIPAMError")
|
|
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
|
return cmdAdd(args)
|
|
})
|
|
Expect(err).To(MatchError("debugPostIPAMError"))
|
|
|
|
// get number of allocated IPs after failure
|
|
after, err := countIPAMIPs(dataDir)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(before).To(Equal(after))
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
if testutils.SpecVersionHasChaining(ver) {
|
|
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules", ver), func() {
|
|
err := originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
tc := testCase{
|
|
ranges: []rangeInfo{{
|
|
subnet: "10.1.2.0/24",
|
|
}},
|
|
ipMasq: true,
|
|
cniVersion: ver,
|
|
}
|
|
|
|
args := tc.createCmdArgs(originalNS, dataDir)
|
|
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
|
return cmdAdd(args)
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
result, err := types100.GetResult(r)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(result.IPs).Should(HaveLen(1))
|
|
|
|
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
rules, err := ipt.List("nat", "POSTROUTING")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
|
|
|
|
err = testutils.CmdDelWithArgs(args, func() error {
|
|
return cmdDel(args)
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return nil
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
for i, tc := range []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",
|
|
},
|
|
},
|
|
} {
|
|
tc := tc
|
|
i := i
|
|
It(fmt.Sprintf("[%s] (%d) configures and deconfigures a bridge and veth with default route with ADD/DEL/CHECK", ver, i), func() {
|
|
tc.cniVersion = ver
|
|
cmdAddDelCheckTest(originalNS, targetNS, tc, dataDir)
|
|
})
|
|
}
|
|
}
|
|
|
|
It(fmt.Sprintf("[%s] configures mac spoof-check (no mac spoofing)", ver), func() {
|
|
Expect(originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
|
|
tc := testCase{
|
|
cniVersion: ver,
|
|
subnet: "10.1.2.0/24",
|
|
macspoofchk: true,
|
|
}
|
|
args := tc.createCmdArgs(originalNS, dataDir)
|
|
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
|
return cmdAdd(args)
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
assertMacSpoofCheckRulesExist()
|
|
|
|
Expect(testutils.CmdDelWithArgs(args, func() error {
|
|
if err := cmdDel(args); err != nil {
|
|
return err
|
|
}
|
|
assertMacSpoofCheckRulesMissing()
|
|
return nil
|
|
})).To(Succeed())
|
|
|
|
return nil
|
|
})).To(Succeed())
|
|
})
|
|
}
|
|
|
|
It("check vlan id when loading net conf", func() {
|
|
type vlanTC struct {
|
|
testCase
|
|
err error
|
|
}
|
|
|
|
createCaseFn := func(ver string, vlan int, err error) vlanTC {
|
|
return vlanTC{
|
|
testCase: testCase{
|
|
cniVersion: ver,
|
|
vlan: vlan,
|
|
},
|
|
err: err,
|
|
}
|
|
}
|
|
|
|
tests := []vlanTC{}
|
|
tests = append(tests, createCaseFn("1.0.0", 0, nil))
|
|
tests = append(tests, createCaseFn("0.4.0", 0, nil))
|
|
tests = append(tests, createCaseFn("1.0.0", -100, fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)")))
|
|
tests = append(tests, createCaseFn("0.4.0", -100, fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)")))
|
|
tests = append(tests, createCaseFn("1.0.0", 5000, fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)")))
|
|
tests = append(tests, createCaseFn("0.4.0", 5000, fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)")))
|
|
|
|
for _, test := range tests {
|
|
_, _, err := loadNetConf([]byte(test.netConfJSON("")), "")
|
|
if test.err == nil {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
} else {
|
|
Expect(err).To(Equal(test.err))
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
func assertMacSpoofCheckRulesExist() {
|
|
assertMacSpoofCheckRules(
|
|
func(actual interface{}, expectedLen int) {
|
|
ExpectWithOffset(3, actual).To(HaveLen(expectedLen))
|
|
})
|
|
}
|
|
|
|
func assertMacSpoofCheckRulesMissing() {
|
|
assertMacSpoofCheckRules(
|
|
func(actual interface{}, _ int) {
|
|
ExpectWithOffset(3, actual).To(BeEmpty())
|
|
})
|
|
}
|
|
|
|
func assertMacSpoofCheckRules(assert func(actual interface{}, expectedLen int)) {
|
|
c, err := nft.ReadConfig()
|
|
ExpectWithOffset(2, err).NotTo(HaveOccurred())
|
|
|
|
expectedTable := nft.NewTable("nat", "bridge")
|
|
filter := nft.TypeFilter
|
|
hook := nft.HookPreRouting
|
|
prio := -300
|
|
policy := nft.PolicyAccept
|
|
expectedBaseChain := nft.NewChain(expectedTable, "PREROUTING", &filter, &hook, &prio, &policy)
|
|
|
|
assert(c.LookupRule(nft.NewRule(
|
|
expectedTable,
|
|
expectedBaseChain,
|
|
nil, nil, nil,
|
|
"macspoofchk-dummy-0-eth0",
|
|
)), 1)
|
|
|
|
assert(c.LookupRule(nft.NewRule(
|
|
expectedTable,
|
|
nft.NewRegularChain(expectedTable, "cni-br-iface-dummy-0-eth0"),
|
|
nil, nil, nil,
|
|
"macspoofchk-dummy-0-eth0",
|
|
)), 1)
|
|
|
|
assert(c.LookupRule(nft.NewRule(
|
|
expectedTable,
|
|
nft.NewRegularChain(expectedTable, "cni-br-iface-dummy-0-eth0-mac"),
|
|
nil, nil, nil,
|
|
"macspoofchk-dummy-0-eth0",
|
|
)), 2)
|
|
}
|