Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for in-container master for vlans
Browse files Browse the repository at this point in the history
Signed-off-by: mmirecki <mmirecki@redhat.com>
mmirecki committed Dec 14, 2022
1 parent ac86731 commit 32207e6
Showing 2 changed files with 298 additions and 210 deletions.
69 changes: 56 additions & 13 deletions plugins/main/vlan/vlan.go
Original file line number Diff line number Diff line change
@@ -20,12 +20,11 @@ import (
"fmt"
"runtime"

"github.com/vishvananda/netlink"

"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/vishvananda/netlink"

"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
@@ -35,9 +34,10 @@ import (

type NetConf struct {
types.NetConf
Master string `json:"master"`
VlanId int `json:"vlanId"`
MTU int `json:"mtu,omitempty"`
Master string `json:"master"`
VlanId int `json:"vlanId"`
MTU int `json:"mtu,omitempty"`
LinkContNs bool `json:"linkInContainer"`
}

func init() {
@@ -47,9 +47,9 @@ func init() {
runtime.LockOSThread()
}

func loadConf(bytes []byte) (*NetConf, string, error) {
func loadConf(args *skel.CmdArgs) (*NetConf, string, error) {
n := &NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
if err := json.Unmarshal(args.StdinData, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Master == "" {
@@ -60,7 +60,25 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
}

// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
var err error
masterMTU := 0
if n.LinkContNs {
netns, err := ns.GetNS(args.Netns)
if err != nil {
return nil, "", fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()

err = netns.Do(func(_ ns.NetNS) error {
masterMTU, err = getMTUByName(n.Master)
if err != nil {
return err
}
return nil
})
} else {
masterMTU, err = getMTUByName(n.Master)
}
if err != nil {
return nil, "", err
}
@@ -82,7 +100,17 @@ func getMTUByName(ifName string) (int, error) {
func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
vlan := &current.Interface{}

m, err := netlink.LinkByName(conf.Master)
var m netlink.Link
var err error
if conf.LinkContNs {
err = netns.Do(func(_ ns.NetNS) error {
m, err = netlink.LinkByName(conf.Master)
return err
})
} else {
m, err = netlink.LinkByName(conf.Master)
}

if err != nil {
return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
}
@@ -104,7 +132,14 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
VlanId: conf.VlanId,
}

if err := netlink.LinkAdd(v); err != nil {
if conf.LinkContNs {
err = netns.Do(func(_ ns.NetNS) error {
return netlink.LinkAdd(v)
})
} else {
err = netlink.LinkAdd(v)
}
if err != nil {
return nil, fmt.Errorf("failed to create vlan: %v", err)
}

@@ -133,7 +168,7 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
}

func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadConf(args.StdinData)
n, cniVersion, err := loadConf(args)
if err != nil {
return err
}
@@ -191,7 +226,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}

func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData)
n, _, err := loadConf(args)
if err != nil {
return err
}
@@ -276,8 +311,16 @@ func cmdCheck(args *skel.CmdArgs) error {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
var m netlink.Link
if conf.LinkContNs {
err = netns.Do(func(_ ns.NetNS) error {
m, err = netlink.LinkByName(conf.Master)
return err
})
} else {
m, err = netlink.LinkByName(conf.Master)
}

m, err := netlink.LinkByName(conf.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
}
439 changes: 242 additions & 197 deletions plugins/main/vlan/vlan_test.go
Original file line number Diff line number Diff line change
@@ -25,9 +25,9 @@ import (

"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/040"
"github.com/containernetworking/cni/pkg/types/100"
types020 "github.com/containernetworking/cni/pkg/types/020"
types040 "github.com/containernetworking/cni/pkg/types/040"
types100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"

@@ -39,6 +39,7 @@ import (
)

const MASTER_NAME = "eth0"
const MASTER_NAME_INCONTAINER = "eth1"

type Net struct {
Name string `json:"name"`
@@ -51,6 +52,7 @@ type Net struct {
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult types100.Result `json:"-"`
LinkContNs bool `json:"linkInContainer"`
}

func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
@@ -197,6 +199,23 @@ var _ = Describe("vlan Operations", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})

err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME_INCONTAINER,
},
})
Expect(err).NotTo(HaveOccurred())
m, err := netlink.LinkByName(MASTER_NAME_INCONTAINER)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(m)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})

@@ -208,283 +227,309 @@ var _ = Describe("vlan Operations", func() {
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
})

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 an vlan link in a non-default namespace with given MTU", ver), func() {
conf := &NetConf{
NetConf: types.NetConf{
CNIVersion: ver,
Name: "testConfig",
Type: "vlan",
},
Master: MASTER_NAME,
VlanId: 33,
MTU: 1500,
}
for _, inContainer := range []bool{false, true} {
masterInterface := MASTER_NAME
if inContainer {
masterInterface = MASTER_NAME_INCONTAINER
}
isInContainer := inContainer // Tests need a local var with constant value
fmt.Sprintf(masterInterface)

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 an vlan link in a non-default namespace with given MTU", ver), func() {
conf := &NetConf{
NetConf: types.NetConf{
CNIVersion: ver,
Name: "testConfig",
Type: "vlan",
},
Master: masterInterface,
VlanId: 33,
MTU: 1500,
LinkContNs: isInContainer,
}

// Create vlan in other namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Create vlan in other namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

_, err := createVlan(conf, "foobar0", targetNS)
_, err := createVlan(conf, "foobar0", targetNS)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())

// Make sure vlan link exists in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Make sure vlan link exists in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

link, err := netlink.LinkByName("foobar0")
link, err := netlink.LinkByName("foobar0")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
Expect(link.Attrs().MTU).To(Equal(1500))
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
Expect(link.Attrs().MTU).To(Equal(1500))
return nil
})
Expect(err).NotTo(HaveOccurred())
})

It(fmt.Sprintf("[%s] creates an vlan link in a non-default namespace with master's MTU", ver), func() {
conf := &NetConf{
NetConf: types.NetConf{
CNIVersion: ver,
Name: "testConfig",
Type: "vlan",
},
Master: MASTER_NAME,
VlanId: 33,
}
It(fmt.Sprintf("[%s] creates an vlan link in a non-default namespace with master's MTU", ver), func() {
conf := &NetConf{
NetConf: types.NetConf{
CNIVersion: ver,
Name: "testConfig",
Type: "vlan",
},
Master: masterInterface,
VlanId: 33,
LinkContNs: isInContainer,
}

// Create vlan in other namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Create vlan in other namespace
otherNs := originalNS
if isInContainer {
otherNs = targetNS
}

m, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(m, 1200)
Expect(err).NotTo(HaveOccurred())
err := otherNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()

m, err := netlink.LinkByName(masterInterface)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(m, 1200)
Expect(err).NotTo(HaveOccurred())

_, err = createVlan(conf, "foobar0", targetNS)
_, err = createVlan(conf, "foobar0", targetNS)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())

// Make sure vlan link exists in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Make sure vlan link exists in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

link, err := netlink.LinkByName("foobar0")
link, err := netlink.LinkByName("foobar0")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
Expect(link.Attrs().MTU).To(Equal(1200))
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
Expect(link.Attrs().MTU).To(Equal(1200))
return nil
})
Expect(err).NotTo(HaveOccurred())
})

It(fmt.Sprintf("[%s] configures and deconfigures a vlan link with ADD/CHECK/DEL", ver), func() {
const IFNAME = "eth0"
It(fmt.Sprintf("[%s] configures and deconfigures a vlan link with ADD/CHECK/DEL", ver), func() {
const IFNAME = "ethX"

conf := fmt.Sprintf(`{
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "vlanTestv4",
"type": "vlan",
"master": "%s",
"vlanId": 1234,
"linkInContainer": %t,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"dataDir": "%s"
}
}`, ver, MASTER_NAME, dataDir)
}`, ver, masterInterface, isInContainer, dataDir)

args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}

args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
t := newTesterByVersion(ver)

t := newTesterByVersion(ver)
var result types.Result
var macAddress string
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

var result types.Result
var macAddress string
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())

var err error
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
macAddress = t.verifyResult(result, IFNAME)
return nil
})
Expect(err).NotTo(HaveOccurred())

macAddress = t.verifyResult(result, IFNAME)
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure vlan link exists in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

// 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))

link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
if macAddress != "" {
hwaddr, err := net.ParseMAC(macAddress)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
}

if macAddress != "" {
hwaddr, err := net.ParseMAC(macAddress)
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
}
Expect(len(addrs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())

addrs, err := netlink.AddrList(link, syscall.AF_INET)
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
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())

n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
newConf, err := buildOneConfig("vlanTestv4", ver, n, result)
Expect(err).NotTo(HaveOccurred())
if isInContainer {
newConf.LinkContNs = true
}

newConf, err := buildOneConfig("vlanTestv4", ver, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())

confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString

args.StdinData = confString
// CNI Check host-device in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
})

// CNI Check host-device in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
})
if testutils.SpecVersionHasCHECK(ver) {
Expect(err).NotTo(HaveOccurred())
} else {
Expect(err).To(MatchError("config version does not allow CHECK"))
}
if testutils.SpecVersionHasCHECK(ver) {
Expect(err).NotTo(HaveOccurred())
} else {
Expect(err).To(MatchError("config version does not allow CHECK"))
}

args.StdinData = []byte(conf)
args.StdinData = []byte(conf)

err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
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()
// 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())
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())

// DEL can be called multiple times, make sure no error is returned
// if the device is already removed.
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// DEL can be called multiple times, make sure no error is returned
// if the device is already removed.
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})

Describe("fails to create vlan link with invalid MTU", func() {
const confFmt = `{
Describe("fails to create vlan link with invalid MTU", func() {
const confFmt = `{
"cniVersion": "%s",
"name": "mynet",
"type": "vlan",
"master": "%s",
"mtu": %d,
"linkInContainer": %t,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"dataDir": "%s"
}
}`

BeforeEach(func() {
var err error
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
}`

// set master link's MTU to 1500
link, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(link, 1500)
Expect(err).NotTo(HaveOccurred())

return nil
})
Expect(err).NotTo(HaveOccurred())
})
BeforeEach(func() {
var err error
sourceNS := originalNS
if isInContainer {
sourceNS = targetNS
}

It(fmt.Sprintf("[%s] fails to create vlan link with greater MTU than master interface", ver), func() {
var err error
err = sourceNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()

args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(confFmt, ver, MASTER_NAME, 1600, dataDir)),
}
// set master link's MTU to 1500
link, err := netlink.LinkByName(masterInterface)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(link, 1500)
Expect(err).NotTo(HaveOccurred())

_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
return nil
})
Expect(err).NotTo(HaveOccurred())
})

_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
It(fmt.Sprintf("[%s] fails to create vlan link with greater MTU than master interface", ver), func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: "ethX",
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, 1600, isInContainer, dataDir)),
}

_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()

_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
return nil
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
return nil
})
})

It(fmt.Sprintf("[%s] fails to create vlan link with negative MTU", ver), func() {
var err error
It(fmt.Sprintf("[%s] fails to create vlan link with negative MTU", ver), func() {
var err error

args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(confFmt, ver, MASTER_NAME, -100, dataDir)),
}
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: "ethX",
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, -100, isInContainer, dataDir)),
}

_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()

_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
return nil
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
return nil
})
})
})
}
}
})

0 comments on commit 32207e6

Please sign in to comment.