Add support for in-container master for vlans

Signed-off-by: mmirecki <mmirecki@redhat.com>
This commit is contained in:
mmirecki 2022-11-10 14:56:54 +01:00
parent ac8673105a
commit 9fa80036d3
2 changed files with 324 additions and 239 deletions

View File

@ -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"
@ -38,6 +37,7 @@ type NetConf struct {
Master string `json:"master"`
VlanId int `json:"vlanId"`
MTU int `json:"mtu,omitempty"`
LinkContNs bool `json:"linkInContainer,omitempty"`
}
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,19 +60,34 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
masterMTU, err := getMTUByName(n.Master, args.Netns, n.LinkContNs)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
func getMTUByName(ifName string, namespace string, inContainer bool) (int, error) {
var link netlink.Link
var err error
if inContainer {
netns, err := ns.GetNS(namespace)
if err != nil {
return 0, fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
err = netns.Do(func(_ ns.NetNS) error {
link, err = netlink.LinkByName(ifName)
return err
})
} else {
link, err = netlink.LinkByName(ifName)
}
if err != nil {
return 0, err
}
@ -82,7 +97,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 +129,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 +165,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 +223,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 +308,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)
}

View File

@ -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) {
@ -198,6 +200,24 @@ var _ = Describe("vlan Operations", func() {
return nil
})
Expect(err).NotTo(HaveOccurred())
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())
})
AfterEach(func() {
@ -208,6 +228,13 @@ var _ = Describe("vlan Operations", func() {
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
})
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
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.
@ -220,9 +247,10 @@ var _ = Describe("vlan Operations", func() {
Name: "testConfig",
Type: "vlan",
},
Master: MASTER_NAME,
Master: masterInterface,
VlanId: 33,
MTU: 1500,
LinkContNs: isInContainer,
}
// Create vlan in other namespace
@ -255,15 +283,21 @@ var _ = Describe("vlan Operations", func() {
Name: "testConfig",
Type: "vlan",
},
Master: MASTER_NAME,
Master: masterInterface,
VlanId: 33,
LinkContNs: isInContainer,
}
// Create vlan in other namespace
err := originalNS.Do(func(ns.NetNS) error {
otherNs := originalNS
if isInContainer {
otherNs = targetNS
}
err := otherNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
m, err := netlink.LinkByName(MASTER_NAME)
m, err := netlink.LinkByName(masterInterface)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(m, 1200)
Expect(err).NotTo(HaveOccurred())
@ -288,7 +322,7 @@ var _ = Describe("vlan Operations", func() {
})
It(fmt.Sprintf("[%s] configures and deconfigures a vlan link with ADD/CHECK/DEL", ver), func() {
const IFNAME = "eth0"
const IFNAME = "ethX"
conf := fmt.Sprintf(`{
"cniVersion": "%s",
@ -296,12 +330,13 @@ var _ = Describe("vlan Operations", func() {
"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",
@ -359,6 +394,9 @@ var _ = Describe("vlan Operations", func() {
newConf, err := buildOneConfig("vlanTestv4", ver, n, result)
Expect(err).NotTo(HaveOccurred())
if isInContainer {
newConf.LinkContNs = true
}
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
@ -370,6 +408,7 @@ var _ = Describe("vlan Operations", func() {
defer GinkgoRecover()
return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
})
if testutils.SpecVersionHasCHECK(ver) {
Expect(err).NotTo(HaveOccurred())
} else {
@ -421,6 +460,7 @@ var _ = Describe("vlan Operations", func() {
"type": "vlan",
"master": "%s",
"mtu": %d,
"linkInContainer": %t,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
@ -430,11 +470,16 @@ var _ = Describe("vlan Operations", func() {
BeforeEach(func() {
var err error
err = originalNS.Do(func(ns.NetNS) error {
sourceNS := originalNS
if isInContainer {
sourceNS = targetNS
}
err = sourceNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// set master link's MTU to 1500
link, err := netlink.LinkByName(MASTER_NAME)
link, err := netlink.LinkByName(masterInterface)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(link, 1500)
Expect(err).NotTo(HaveOccurred())
@ -446,12 +491,11 @@ var _ = Describe("vlan Operations", func() {
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: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(confFmt, ver, MASTER_NAME, 1600, dataDir)),
Netns: targetNS.Path(),
IfName: "ethX",
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, 1600, isInContainer, dataDir)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
@ -470,9 +514,9 @@ var _ = Describe("vlan Operations", func() {
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(confFmt, ver, MASTER_NAME, -100, dataDir)),
Netns: targetNS.Path(),
IfName: "ethX",
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, -100, isInContainer, dataDir)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
@ -487,4 +531,5 @@ var _ = Describe("vlan Operations", func() {
})
})
}
}
})