Merge pull request #636 from EdDev/bridge-mac-specification
bridge: Add mac field to specify container iface mac
This commit is contained in:
commit
f14ff6687a
@ -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.
|
||||
|
@ -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() {
|
||||
|
@ -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 := ¤t.Interface{}
|
||||
hostIface := ¤t.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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -66,7 +66,7 @@ func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Resu
|
||||
containerInterface := ¤t.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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user