diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 3eca082724a..d23059cdc8c 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -15,6 +15,7 @@ package agent import ( + "bytes" "context" "encoding/json" "fmt" @@ -77,6 +78,12 @@ var ( // getTransportIPNetDeviceByName is meant to be overridden for testing. getTransportIPNetDeviceByName = GetTransportIPNetDeviceByName + + // setLinkUp is meant to be overridden for testing + setLinkUp = util.SetLinkUp + + // configureLinkAddresses is meant to be overridden for testing + configureLinkAddresses = util.ConfigureLinkAddresses ) // otherConfigKeysForIPsecCertificates are configurations added to OVS bridge when AuthenticationMode is "cert" and @@ -243,6 +250,7 @@ func (i *Initializer) initInterfaceStore() error { intf := &interfacestore.InterfaceConfig{ Type: interfacestore.GatewayInterface, InterfaceName: port.Name, + MAC: port.MAC, OVSPortConfig: ovsPort} if intf.InterfaceName != i.hostGateway { klog.Warningf("The discovered gateway interface name %s is different from the configured value: %s", @@ -639,7 +647,8 @@ func (i *Initializer) setupGatewayInterface() error { externalIDs := map[string]interface{}{ interfacestore.AntreaInterfaceTypeKey: interfacestore.AntreaGateway, } - gwPortUUID, err := i.ovsBridgeClient.CreateInternalPort(i.hostGateway, config.HostGatewayOFPort, "", externalIDs) + mac := util.GenerateRandomMAC() + gwPortUUID, err := i.ovsBridgeClient.CreateInternalPort(i.hostGateway, config.HostGatewayOFPort, mac.String(), externalIDs) if err != nil { klog.ErrorS(err, "Failed to create gateway port on OVS bridge", "port", i.hostGateway) return err @@ -650,7 +659,7 @@ func (i *Initializer) setupGatewayInterface() error { return err } klog.InfoS("Allocated OpenFlow port for gateway interface", "port", i.hostGateway, "ofPort", gwPort) - gatewayIface = interfacestore.NewGatewayInterface(i.hostGateway) + gatewayIface = interfacestore.NewGatewayInterface(i.hostGateway, mac) gatewayIface.OVSPortConfig = &interfacestore.OVSPortConfig{PortUUID: gwPortUUID, OFPort: gwPort} i.ifaceStore.AddInterface(gatewayIface) } else { @@ -658,7 +667,7 @@ func (i *Initializer) setupGatewayInterface() error { } // Idempotent operation to set the gateway's MTU: we perform this operation regardless of - // whether or not the gateway interface already exists, as the desired MTU may change across + // whether the gateway interface already exists, as the desired MTU may change across // restarts. klog.V(4).Infof("Setting gateway interface %s MTU to %d", i.hostGateway, i.nodeConfig.NodeMTU) @@ -679,7 +688,7 @@ func (i *Initializer) configureGatewayInterface(gatewayIface *interfacestore.Int // Host link might not be queried at once after creating OVS internal port; retry max 5 times with 1s // delay each time to ensure the link is ready. for retry := 0; retry < maxRetryForHostLink; retry++ { - gwMAC, gwLinkIdx, err = util.SetLinkUp(i.hostGateway) + gwMAC, gwLinkIdx, err = setLinkUp(i.hostGateway) if err == nil { break } @@ -695,9 +704,17 @@ func (i *Initializer) configureGatewayInterface(gatewayIface *interfacestore.Int klog.Errorf("Failed to find host link for gateway %s: %v", i.hostGateway, err) return err } - + // Persist the MAC configured in the network interface when the gatewayIface.MAC is not set. This may + // happen in upgrade case. + // Note the "mac" field in Windows OVS internal Interface has no impact on the network adapter's actual MAC, + // set it to the same value just to keep consistency. + if bytes.Compare(gatewayIface.MAC, gwMAC) != 0 { + gatewayIface.MAC = gwMAC + if err := i.ovsBridgeClient.SetInterfaceMAC(gatewayIface.InterfaceName, gwMAC); err != nil { + klog.ErrorS(err, "Failed to persist interface MAC address", "interface", gatewayIface.InterfaceName, "mac", gwMAC) + } + } i.nodeConfig.GatewayConfig = &config.GatewayConfig{Name: i.hostGateway, MAC: gwMAC, OFPort: uint32(gatewayIface.OFPort)} - gatewayIface.MAC = gwMAC gatewayIface.IPs = []net.IP{} if i.networkConfig.TrafficEncapMode.IsNetworkPolicyOnly() { // Assign IP to gw as required by SpoofGuard. @@ -1146,13 +1163,13 @@ func (i *Initializer) allocateGatewayAddresses(localSubnets []*net.IPNet, gatewa // (i.e. portExists is false). Indeed, it may be possible for the interface to exist even if the OVS bridge does // not exist. // Configure any missing IP address on the interface. Remove any extra IP address that may exist. - if err := util.ConfigureLinkAddresses(i.nodeConfig.GatewayConfig.LinkIndex, gwIPs); err != nil { + if err := configureLinkAddresses(i.nodeConfig.GatewayConfig.LinkIndex, gwIPs); err != nil { return err } // Periodically check whether IP configuration of the gateway is correct. // Terminate when stopCh is closed. go wait.Until(func() { - if err := util.ConfigureLinkAddresses(i.nodeConfig.GatewayConfig.LinkIndex, gwIPs); err != nil { + if err := configureLinkAddresses(i.nodeConfig.GatewayConfig.LinkIndex, gwIPs); err != nil { klog.Errorf("Failed to check IP configuration of the gateway: %v", err) } }, 60*time.Second, i.stopCh) diff --git a/pkg/agent/agent_linux_test.go b/pkg/agent/agent_linux_test.go new file mode 100644 index 00000000000..5abb5cc4e90 --- /dev/null +++ b/pkg/agent/agent_linux_test.go @@ -0,0 +1,19 @@ +// Copyright 2022 Antrea 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 agent + +func mockSetInterfaceMTU(returnErr error) func() { + return func() {} +} diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index 7e4dd7dfce1..dd179481ac0 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -578,3 +578,72 @@ func TestSetupDefaultTunnelInterface(t *testing.T) { }) } } + +func TestSetupGatewayInterface(t *testing.T) { + fakeMAC, _ := net.ParseMAC("12:34:56:78:76:54") + defer mockSetLinkUp(fakeMAC, 10, nil)() + defer mockConfigureLinkAddress(nil)() + defer mockSetInterfaceMTU(nil)() + + controller := mock.NewController(t) + defer controller.Finish() + + podCIDRStr := "172.16.10.0/24" + _, podCIDR, _ := net.ParseCIDR(podCIDRStr) + nodeConfig := &config.NodeConfig{ + Name: "n1", + Type: config.K8sNode, + OVSBridge: "br-int", + PodIPv4CIDR: podCIDR, + NodeMTU: 1450, + } + networkConfig := &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + TunnelType: ovsconfig.GeneveTunnel, + TunnelCsum: false, + } + + mockOVSBridgeClient := ovsconfigtest.NewMockOVSBridgeClient(controller) + client := fake.NewSimpleClientset() + ifaceStore := interfacestore.NewInterfaceStore() + stopCh := make(chan struct{}) + initializer := &Initializer{ + client: client, + ifaceStore: ifaceStore, + ovsBridgeClient: mockOVSBridgeClient, + ovsBridge: "br-int", + networkConfig: networkConfig, + nodeConfig: nodeConfig, + hostGateway: "antrea-gw0", + stopCh: stopCh, + } + close(stopCh) + portUUID := "123456780a" + ofport := int32(config.HostGatewayOFPort) + mockOVSBridgeClient.EXPECT().CreateInternalPort(initializer.hostGateway, ofport, mock.Any(), mock.Any()).Return(portUUID, nil) + mockOVSBridgeClient.EXPECT().SetInterfaceMAC(initializer.hostGateway, fakeMAC).Return(nil) + mockOVSBridgeClient.EXPECT().GetOFPort(initializer.hostGateway, false).Return(ofport, nil) + mockOVSBridgeClient.EXPECT().SetInterfaceMTU(initializer.hostGateway, nodeConfig.NodeMTU).Return(nil) + err := initializer.setupGatewayInterface() + assert.NoError(t, err) +} + +func mockSetLinkUp(returnedMAC net.HardwareAddr, returnIndex int, returnErr error) func() { + originalSetLinkUp := setLinkUp + setLinkUp = func(name string) (net.HardwareAddr, int, error) { + return returnedMAC, returnIndex, returnErr + } + return func() { + setLinkUp = originalSetLinkUp + } +} + +func mockConfigureLinkAddress(returnedErr error) func() { + originalConfigureLinkAddresses := configureLinkAddresses + configureLinkAddresses = func(idx int, ipNets []*net.IPNet) error { + return returnedErr + } + return func() { + configureLinkAddresses = originalConfigureLinkAddresses + } +} diff --git a/pkg/agent/agent_windows.go b/pkg/agent/agent_windows.go index 054817f00ca..b85bda53692 100644 --- a/pkg/agent/agent_windows.go +++ b/pkg/agent/agent_windows.go @@ -34,6 +34,11 @@ import ( utilip "antrea.io/antrea/pkg/util/ip" ) +var ( + // setInterfaceMTU is meant to be overridden for testing + setInterfaceMTU = util.SetInterfaceMTU +) + func (i *Initializer) prepareHostNetwork() error { if i.nodeConfig.Type == config.K8sNode { return i.prepareHNSNetworkAndOVSExtension() @@ -443,7 +448,7 @@ func (i *Initializer) setInterfaceMTU(iface string, mtu int) error { if err := i.ovsBridgeClient.SetInterfaceMTU(iface, mtu); err != nil { return err } - return util.SetInterfaceMTU(iface, mtu) + return setInterfaceMTU(iface, mtu) } func (i *Initializer) setVMNodeConfig(en *v1alpha1.ExternalNode, nodeName string) error { diff --git a/pkg/agent/agent_windows_test.go b/pkg/agent/agent_windows_test.go new file mode 100644 index 00000000000..97c04f33c16 --- /dev/null +++ b/pkg/agent/agent_windows_test.go @@ -0,0 +1,25 @@ +// Copyright 2022 Antrea 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 agent + +func mockSetInterfaceMTU(returnErr error) func() { + originalSetInterfaceMTU := setInterfaceMTU + setInterfaceMTU = func(ifaceName string, mtu int) error { + return returnErr + } + return func() { + setInterfaceMTU = originalSetInterfaceMTU + } +} diff --git a/pkg/agent/cniserver/interface_configuration_linux.go b/pkg/agent/cniserver/interface_configuration_linux.go index eced5fe74ae..a45264ac917 100644 --- a/pkg/agent/cniserver/interface_configuration_linux.go +++ b/pkg/agent/cniserver/interface_configuration_linux.go @@ -37,6 +37,7 @@ import ( "antrea.io/antrea/pkg/agent/util/ndp" cnipb "antrea.io/antrea/pkg/apis/cni/v1beta1" "antrea.io/antrea/pkg/ovs/ovsconfig" + cniip "antrea.io/antrea/third_party/containernetworking/ip" ) // NetDeviceType type Enum @@ -254,13 +255,14 @@ func (ic *ifConfigurator) configureContainerLinkVeth( containerIface := ¤t.Interface{Name: containerIfaceName, Sandbox: containerNetNS} result.Interfaces = []*current.Interface{hostIface, containerIface} + podMAC := util.GenerateRandomMAC() if err := ns.WithNetNSPath(containerNetNS, func(hostNS ns.NetNS) error { klog.V(2).Infof("Creating veth devices (%s, %s) for container %s", containerIfaceName, hostIfaceName, containerID) - hostVeth, containerVeth, err := ip.SetupVethWithName(containerIfaceName, hostIfaceName, mtu, hostNS) + hostVeth, containerVeth, err := cniip.SetupVethWithName(containerIfaceName, hostIfaceName, mtu, podMAC.String(), hostNS) if err != nil { return fmt.Errorf("failed to create veth devices for container %s: %v", containerID, err) } - containerIface.Mac = containerVeth.HardwareAddr.String() + containerIface.Mac = podMAC.String() hostIface.Mac = hostVeth.HardwareAddr.String() // Disable TX checksum offloading when it's configured explicitly. if ic.disableTXChecksumOffload { diff --git a/pkg/agent/interfacestore/interface_cache_test.go b/pkg/agent/interfacestore/interface_cache_test.go index 37ec02e7d22..de41c9a597e 100644 --- a/pkg/agent/interfacestore/interface_cache_test.go +++ b/pkg/agent/interfacestore/interface_cache_test.go @@ -89,7 +89,7 @@ func testContainerInterface(t *testing.T) { } func testGatewayInterface(t *testing.T) { - gatewayInterface := NewGatewayInterface("antrea-gw0") + gatewayInterface := NewGatewayInterface("antrea-gw0", util.GenerateRandomMAC()) gatewayInterface.IPs = []net.IP{gwIP} gatewayInterface.OVSPortConfig = &OVSPortConfig{ OFPort: 13, diff --git a/pkg/agent/interfacestore/types.go b/pkg/agent/interfacestore/types.go index 903af7169ee..b0e03a31b35 100644 --- a/pkg/agent/interfacestore/types.go +++ b/pkg/agent/interfacestore/types.go @@ -146,8 +146,8 @@ func NewContainerInterface( } // NewGatewayInterface creates InterfaceConfig for the host gateway interface. -func NewGatewayInterface(gatewayName string) *InterfaceConfig { - gatewayConfig := &InterfaceConfig{InterfaceName: gatewayName, Type: GatewayInterface} +func NewGatewayInterface(gatewayName string, gatewayMAC net.HardwareAddr) *InterfaceConfig { + gatewayConfig := &InterfaceConfig{InterfaceName: gatewayName, Type: GatewayInterface, MAC: gatewayMAC} return gatewayConfig } diff --git a/pkg/agent/util/net.go b/pkg/agent/util/net.go index 33fdc487dab..46c254d0365 100644 --- a/pkg/agent/util/net.go +++ b/pkg/agent/util/net.go @@ -406,8 +406,9 @@ func GenerateRandomMAC() net.HardwareAddr { if _, err := rand.Read(buf); err != nil { klog.ErrorS(err, "Failed to generate a random MAC") } - // Set the local bit - buf[0] |= 2 + // Unset the multicast bit. + buf[0] &= 0xfe + buf[0] |= 0x02 return buf } diff --git a/pkg/agent/util/net_test.go b/pkg/agent/util/net_test.go index 30b4528630e..f58a8f1a4b9 100644 --- a/pkg/agent/util/net_test.go +++ b/pkg/agent/util/net_test.go @@ -104,3 +104,15 @@ func TestExtendCIDRWithIP(t *testing.T) { assert.Equal(t, expectedIPNet, gotIPNet) } } + +func TestGenerateRandomMAC(t *testing.T) { + validateBits := func(mac net.HardwareAddr) (byte, byte) { + localBit := mac[0] & 0x2 >> 1 + mcastBit := mac[0] & 0x1 + return localBit, mcastBit + } + mac1 := GenerateRandomMAC() + localBit, mcastBit := validateBits(mac1) + assert.Equal(t, uint8(1), localBit) + assert.Equal(t, uint8(0), mcastBit) +} diff --git a/pkg/ovs/ovsconfig/interfaces.go b/pkg/ovs/ovsconfig/interfaces.go index ad82f213fef..209c74c2d01 100644 --- a/pkg/ovs/ovsconfig/interfaces.go +++ b/pkg/ovs/ovsconfig/interfaces.go @@ -14,6 +14,10 @@ package ovsconfig +import ( + "net" +) + type TunnelType string type OVSDatapathType string @@ -64,4 +68,5 @@ type OVSBridgeClient interface { GetOVSDatapathType() OVSDatapathType SetInterfaceType(name, ifType string) Error SetPortExternalIDs(portName string, externalIDs map[string]interface{}) Error + SetInterfaceMAC(name string, mac net.HardwareAddr) Error } diff --git a/pkg/ovs/ovsconfig/ovs_client.go b/pkg/ovs/ovsconfig/ovs_client.go index bcc1d7c9ffe..b73211adaba 100644 --- a/pkg/ovs/ovsconfig/ovs_client.go +++ b/pkg/ovs/ovsconfig/ovs_client.go @@ -48,6 +48,7 @@ type OVSPortData struct { OFPort int32 ExternalIDs map[string]string Options map[string]string + MAC net.HardwareAddr } const ( @@ -694,6 +695,19 @@ func buildPortDataCommon(port, intf map[string]interface{}, portData *OVSPortDat } else { // ofport not assigned by OVS yet portData.OFPort = 0 } + var macStr string + if field, ok := intf["mac"].(string); ok { + macStr = field + } else if fields, ok := intf["mac"].([]interface{}); ok { + if len(fields) > 0 { + macStr = fields[0].(string) + } + } + if macStr != "" { + if mac, err := net.ParseMAC(macStr); err == nil { + portData.MAC = mac + } + } } // GetPortData retrieves port data given the OVS port UUID and interface name. @@ -709,7 +723,7 @@ func (br *OVSBridge) GetPortData(portUUID, ifName string) (*OVSPortData, Error) }) tx.Select(dbtransaction.Select{ Table: "Interface", - Columns: []string{"_uuid", "type", "ofport", "options"}, + Columns: []string{"_uuid", "type", "ofport", "options", "mac"}, Where: [][]interface{}{{"name", "==", ifName}}, }) @@ -762,7 +776,7 @@ func (br *OVSBridge) GetPortList() ([]OVSPortData, Error) { }) tx.Select(dbtransaction.Select{ Table: "Interface", - Columns: []string{"_uuid", "type", "name", "ofport", "options"}, + Columns: []string{"_uuid", "type", "name", "ofport", "options", "mac"}, }) res, err, temporary := tx.Commit() @@ -1097,3 +1111,24 @@ func (br *OVSBridge) SetInterfaceMTU(name string, MTU int) error { return nil } + +func (br *OVSBridge) SetInterfaceMAC(name string, mac net.HardwareAddr) Error { + tx := br.ovsdb.Transaction(openvSwitchSchema) + + tx.Update(dbtransaction.Update{ + Table: "Interface", + Where: [][]interface{}{{"name", "==", name}}, + Row: map[string]interface{}{ + "mac": mac.String(), + }, + }) + + _, err, temporary := tx.Commit() + if err != nil { + klog.Error("Transaction failed: ", err) + return NewTransactionError(err, temporary) + } + + return nil + +} diff --git a/pkg/ovs/ovsconfig/ovs_client_test.go b/pkg/ovs/ovsconfig/ovs_client_test.go index f8e24a1a00e..35daad09b8c 100644 --- a/pkg/ovs/ovsconfig/ovs_client_test.go +++ b/pkg/ovs/ovsconfig/ovs_client_test.go @@ -15,6 +15,7 @@ package ovsconfig import ( + "net" "testing" "github.com/stretchr/testify/assert" @@ -37,3 +38,83 @@ func TestOVSClient(t *testing.T) { assert.NoError(t, err) } + +func TestBuildPortDataCommon(t *testing.T) { + macStr := "9a:23:45:23:22:41" + intfMAC, _ := net.ParseMAC(macStr) + for _, tc := range []struct { + name string + port map[string]interface{} + intf map[string]interface{} + portData *OVSPortData + }{ + { + name: "gw-port", + port: map[string]interface{}{"name": "antrea-gw0", "external_ids": []interface{}{"map", []interface{}{[]interface{}{"antrea-type", "gateway"}}}}, + intf: map[string]interface{}{"name": "antrea-gw0", "mac": macStr, "type": "internal", "ofport": float64(2), "options": []interface{}{""}}, + portData: &OVSPortData{ + Name: "antrea-gw0", + ExternalIDs: map[string]string{"antrea-type": "gateway"}, + Options: map[string]string{}, + IFType: "internal", + OFPort: 2, + MAC: intfMAC, + }, + }, { + name: "tun-port", + port: map[string]interface{}{"name": "antrea-tun0", "external_ids": []interface{}{"map", []interface{}{[]interface{}{"antrea-type", "tunnel"}}}}, + intf: map[string]interface{}{"name": "antrea-tun0", "mac": macStr, "type": "geneve", "ofport": float64(1), "options": []interface{}{"map", []interface{}{[]interface{}{"key", "flow"}, []interface{}{"remote_ip", "flow"}}}}, + portData: &OVSPortData{ + Name: "antrea-tun0", + ExternalIDs: map[string]string{"antrea-type": "tunnel"}, + Options: map[string]string{"key": "flow", "remote_ip": "flow"}, + IFType: "geneve", + OFPort: 1, + MAC: intfMAC, + }, + }, { + name: "general-port", + port: map[string]interface{}{"name": "p0", "external_ids": []interface{}{"map", []interface{}{[]interface{}{"antrea-type", "container"}, []interface{}{"ip", "1.2.3.4"}}}}, + intf: map[string]interface{}{"name": "p0", "mac": []interface{}{macStr}, "type": "", "ofport": float64(3), "options": []interface{}{""}}, + portData: &OVSPortData{ + Name: "p0", + ExternalIDs: map[string]string{"antrea-type": "container", "ip": "1.2.3.4"}, + Options: map[string]string{}, + IFType: "", + OFPort: 3, + MAC: intfMAC, + }, + }, { + name: "access-port", + port: map[string]interface{}{"name": "p1", "tag": float64(10), "external_ids": []interface{}{"map", []interface{}{[]interface{}{"antrea-type", "container"}, []interface{}{"ip", "1.2.3.5"}}}}, + intf: map[string]interface{}{"name": "p1", "mac": macStr, "type": "", "ofport": float64(3), "options": []interface{}{""}}, + portData: &OVSPortData{ + Name: "p1", + ExternalIDs: map[string]string{"antrea-type": "container", "ip": "1.2.3.5"}, + Options: map[string]string{}, + IFType: "", + OFPort: 3, + VLANID: 10, + MAC: intfMAC, + }, + }, { + name: "no-mac-port", + port: map[string]interface{}{"name": "p2", "external_ids": []interface{}{"map", []interface{}{[]interface{}{"antrea-type", "container"}, []interface{}{"ip", "1.2.3.5"}}}}, + intf: map[string]interface{}{"name": "p2", "mac": []interface{}{}, "type": "", "ofport": float64(4), "options": []interface{}{""}}, + portData: &OVSPortData{ + Name: "p2", + ExternalIDs: map[string]string{"antrea-type": "container", "ip": "1.2.3.5"}, + Options: map[string]string{}, + IFType: "", + OFPort: 4, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + portData := &OVSPortData{} + buildPortDataCommon(tc.port, tc.intf, portData) + assert.Equal(t, tc.portData, portData) + }) + } + +} diff --git a/pkg/ovs/ovsconfig/testing/mock_ovsconfig.go b/pkg/ovs/ovsconfig/testing/mock_ovsconfig.go index db30c89f87c..f95f45ea711 100644 --- a/pkg/ovs/ovsconfig/testing/mock_ovsconfig.go +++ b/pkg/ovs/ovsconfig/testing/mock_ovsconfig.go @@ -22,6 +22,7 @@ package testing import ( ovsconfig "antrea.io/antrea/pkg/ovs/ovsconfig" gomock "github.com/golang/mock/gomock" + net "net" reflect "reflect" ) @@ -455,6 +456,20 @@ func (mr *MockOVSBridgeClientMockRecorder) SetExternalIDs(arg0 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetExternalIDs", reflect.TypeOf((*MockOVSBridgeClient)(nil).SetExternalIDs), arg0) } +// SetInterfaceMAC mocks base method +func (m *MockOVSBridgeClient) SetInterfaceMAC(arg0 string, arg1 net.HardwareAddr) ovsconfig.Error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetInterfaceMAC", arg0, arg1) + ret0, _ := ret[0].(ovsconfig.Error) + return ret0 +} + +// SetInterfaceMAC indicates an expected call of SetInterfaceMAC +func (mr *MockOVSBridgeClientMockRecorder) SetInterfaceMAC(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetInterfaceMAC", reflect.TypeOf((*MockOVSBridgeClient)(nil).SetInterfaceMAC), arg0, arg1) +} + // SetInterfaceMTU mocks base method func (m *MockOVSBridgeClient) SetInterfaceMTU(arg0 string, arg1 int) error { m.ctrl.T.Helper() diff --git a/third_party/containernetworking/ip/link_linux.go b/third_party/containernetworking/ip/link_linux.go new file mode 100644 index 00000000000..12b35100763 --- /dev/null +++ b/third_party/containernetworking/ip/link_linux.go @@ -0,0 +1,152 @@ +// Copyright 2015 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 ip + +import ( + "crypto/rand" + "fmt" + "net" + "os" + + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/utils/sysctl" + "github.com/vishvananda/netlink" +) + +// makeVethPair is called from within the container's network namespace +func makeVethPair(name, peer string, mtu int, mac string, hostNS ns.NetNS) (netlink.Link, error) { + veth := &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: name, + MTU: mtu, + }, + PeerName: peer, + PeerNamespace: netlink.NsFd(int(hostNS.Fd())), + } + 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 + } + // Re-fetch the container link to get its creation-time parameters, e.g. index and mac + veth2, err := netlink.LinkByName(name) + if err != nil { + netlink.LinkDel(veth) // try and clean up the link if possible. + return nil, err + } + + return veth2, nil +} + +func peerExists(name string) bool { + if _, err := netlink.LinkByName(name); err != nil { + return false + } + return true +} + +func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (peerName string, veth netlink.Link, err error) { + for i := 0; i < 10; i++ { + if vethPeerName != "" { + peerName = vethPeerName + } else { + peerName, err = RandomVethName() + if err != nil { + return + } + } + + veth, err = makeVethPair(name, peerName, mtu, mac, hostNS) + switch { + case err == nil: + return + + case os.IsExist(err): + if peerExists(peerName) && vethPeerName == "" { + continue + } + err = fmt.Errorf("container veth name provided (%v) already exists", name) + return + + default: + err = fmt.Errorf("failed to make veth pair: %v", err) + return + } + } + + // should really never be hit + err = fmt.Errorf("failed to find a unique veth name") + return +} + +// RandomVethName returns string "veth" with random prefix (hashed from entropy) +func RandomVethName() (string, error) { + entropy := make([]byte, 4) + _, err := rand.Read(entropy) + if err != nil { + return "", fmt.Errorf("failed to generate random veth name: %v", err) + } + + // NetworkManager (recent versions) will ignore veth devices that start with "veth" + return fmt.Sprintf("veth%x", entropy), nil +} + +func ifaceFromNetlinkLink(l netlink.Link) net.Interface { + a := l.Attrs() + return net.Interface{ + Index: a.Index, + MTU: a.MTU, + Name: a.Name, + HardwareAddr: a.HardwareAddr, + Flags: a.Flags, + } +} + +// SetupVethWithName sets up a pair of virtual ethernet devices. +// Call SetupVethWithName from inside the container netns. It will create both veth +// 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, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) { + hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu, contVethMac, hostNS) + if err != nil { + return net.Interface{}, net.Interface{}, err + } + + var hostVeth netlink.Link + err = hostNS.Do(func(_ ns.NetNS) error { + hostVeth, err = netlink.LinkByName(hostVethName) + if err != nil { + return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Path(), err) + } + + if err = netlink.LinkSetUp(hostVeth); err != nil { + return fmt.Errorf("failed to set %q up: %v", hostVethName, err) + } + + // we want to own the routes for this interface + _, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", hostVethName), "0") + return nil + }) + if err != nil { + return net.Interface{}, net.Interface{}, err + } + return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil +}