Merge pull request #636 from EdDev/bridge-mac-specification

bridge: Add mac field to specify container iface mac
This commit is contained in:
Dan Williams 2021-06-30 10:57:09 -05:00 committed by GitHub
commit f14ff6687a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 199 additions and 24 deletions

View File

@ -32,7 +32,7 @@ var (
ErrLinkNotFound = errors.New("link not found")
)
func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
func makeVethPair(name, peer string, mtu int, mac string) (netlink.Link, error) {
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: name,
@ -41,6 +41,13 @@ func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
},
PeerName: peer,
}
if mac != "" {
m, err := net.ParseMAC(mac)
if err != nil {
return nil, err
}
veth.LinkAttrs.HardwareAddr = m
}
if err := netlink.LinkAdd(veth); err != nil {
return nil, err
}
@ -61,7 +68,7 @@ func peerExists(name string) bool {
return true
}
func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink.Link, err error) {
func makeVeth(name, vethPeerName string, mtu int, mac string) (peerName string, veth netlink.Link, err error) {
for i := 0; i < 10; i++ {
if vethPeerName != "" {
peerName = vethPeerName
@ -72,7 +79,7 @@ func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink
}
}
veth, err = makeVethPair(name, peerName, mtu)
veth, err = makeVethPair(name, peerName, mtu, mac)
switch {
case err == nil:
return
@ -131,8 +138,8 @@ func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
// devices and move the host-side veth into the provided hostNS namespace.
// hostVethName: If hostVethName is not specified, the host-side veth name will use a random string.
// On success, SetupVethWithName returns (hostVeth, containerVeth, nil)
func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu)
func SetupVethWithName(contVethName, hostVethName string, mtu int, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu, contVethMac)
if err != nil {
return net.Interface{}, net.Interface{}, err
}
@ -174,8 +181,8 @@ func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.Net
// Call SetupVeth from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
return SetupVethWithName(contVethName, "", mtu, hostNS)
func SetupVeth(contVethName string, mtu int, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
return SetupVethWithName(contVethName, "", mtu, contVethMac, hostNS)
}
// DelLinkByName removes an interface link.

View File

@ -70,7 +70,7 @@ var _ = Describe("Link", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, containerVeth, err = ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, hostNetNS)
hostVeth, containerVeth, err = ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, "", hostNetNS)
if err != nil {
return err
}
@ -157,7 +157,7 @@ var _ = Describe("Link", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
return nil
@ -187,7 +187,7 @@ var _ = Describe("Link", func() {
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(HavePrefix("failed to move veth to host netns: "))
return nil
@ -205,7 +205,7 @@ var _ = Describe("Link", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err).NotTo(HaveOccurred())
hostVethName = hostVeth.Name
return nil
@ -231,6 +231,32 @@ var _ = Describe("Link", func() {
})
})
It("successfully creates a veth pair with an explicit mac", func() {
const mac = "02:00:00:00:01:23"
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, mac, hostNetNS)
Expect(err).NotTo(HaveOccurred())
hostVethName = hostVeth.Name
link, err := netlink.LinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr.String()).To(Equal(mac))
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(hostVethName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(mac))
return nil
})
})
})
It("DelLinkByName must delete the veth endpoints", func() {

View File

@ -55,6 +55,25 @@ type NetConf struct {
HairpinMode bool `json:"hairpinMode"`
PromiscMode bool `json:"promiscMode"`
Vlan int `json:"vlan"`
Args struct {
Cni BridgeArgs `json:"cni,omitempty"`
} `json:"args,omitempty"`
RuntimeConfig struct {
Mac string `json:"mac,omitempty"`
} `json:"runtimeConfig,omitempty"`
mac string
}
type BridgeArgs struct {
Mac string `json:"mac,omitempty"`
}
// MacEnvArgs represents CNI_ARGS
type MacEnvArgs struct {
types.CommonArgs
MAC types.UnmarshallableString `json:"mac,omitempty"`
}
type gwInfo struct {
@ -70,7 +89,7 @@ func init() {
runtime.LockOSThread()
}
func loadNetConf(bytes []byte) (*NetConf, string, error) {
func loadNetConf(bytes []byte, envArgs string) (*NetConf, string, error) {
n := &NetConf{
BrName: defaultBrName,
}
@ -80,6 +99,26 @@ func loadNetConf(bytes []byte) (*NetConf, string, error) {
if n.Vlan < 0 || n.Vlan > 4094 {
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
}
if envArgs != "" {
e := MacEnvArgs{}
if err := types.LoadArgs(envArgs, &e); err != nil {
return nil, "", err
}
if e.MAC != "" {
n.mac = string(e.MAC)
}
}
if mac := n.Args.Cni.Mac; mac != "" {
n.mac = mac
}
if mac := n.RuntimeConfig.Mac; mac != "" {
n.mac = mac
}
return n, n.CNIVersion, nil
}
@ -273,7 +312,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanId int) (netlink.Link, error) {
return nil, fmt.Errorf("faild to find host namespace: %v", err)
}
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanId)
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanId, "")
if err != nil {
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
}
@ -287,13 +326,13 @@ func ensureVlanInterface(br *netlink.Bridge, vlanId int) (netlink.Link, error) {
return brGatewayVeth, nil
}
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int) (*current.Interface, *current.Interface, error) {
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int, mac string) (*current.Interface, *current.Interface, error) {
contIface := &current.Interface{}
hostIface := &current.Interface{}
err := netns.Do(func(hostNS ns.NetNS) error {
// create the veth pair in the container and move host end into host netns
hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS)
hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, mac, hostNS)
if err != nil {
return err
}
@ -380,7 +419,7 @@ func enableIPForward(family int) error {
func cmdAdd(args *skel.CmdArgs) error {
var success bool = false
n, cniVersion, err := loadNetConf(args.StdinData)
n, cniVersion, err := loadNetConf(args.StdinData, args.Args)
if err != nil {
return err
}
@ -406,7 +445,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer netns.Close()
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan)
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.mac)
if err != nil {
return err
}
@ -585,7 +624,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData)
n, _, err := loadNetConf(args.StdinData, args.Args)
if err != nil {
return err
}
@ -776,7 +815,7 @@ func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error)
func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData)
n, _, err := loadNetConf(args.StdinData, args.Args)
if err != nil {
return err
}

View File

@ -78,6 +78,21 @@ type testCase struct {
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
@ -148,6 +163,18 @@ const (
ipamEndStr = `
}`
argsFormat = `,
"args": {
"cni": {
"mac": %q
}
}`
runtimeConfig = `,
"RuntimeConfig": {
"mac": %q
}`
)
// netConfJSON() generates a JSON network configuration string
@ -160,6 +187,12 @@ func (tc testCase) netConfJSON(dataDir string) string {
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.isLayer2 {
conf += netDefault
@ -223,6 +256,7 @@ func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArg
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
Args: tc.envArgs,
}
}
@ -428,7 +462,10 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
Expect(result.Interfaces[2].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
@ -725,7 +762,10 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
Expect(result.Interfaces[2].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
@ -1022,7 +1062,10 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
Expect(result.Interfaces[2].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
@ -1967,6 +2010,66 @@ var _ = Describe("bridge Operations", func() {
})
}
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()
@ -2099,7 +2202,7 @@ var _ = Describe("bridge Operations", func() {
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("")))
_, _, err := loadNetConf([]byte(test.netConfJSON("")), "")
if test.err == nil {
Expect(err).To(BeNil())
} else {

View File

@ -66,7 +66,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu
containerInterface := &current.Interface{}
err := netns.Do(func(hostNS ns.NetNS) error {
hostVeth, contVeth0, err := ip.SetupVeth(ifName, mtu, hostNS)
hostVeth, contVeth0, err := ip.SetupVeth(ifName, mtu, "", hostNS)
if err != nil {
return err
}