diff --git a/examples/ovs-net-vlan-ipam.yml b/examples/ovs-net-vlan-ipam.yml new file mode 100644 index 000000000..ef628aa4c --- /dev/null +++ b/examples/ovs-net-vlan-ipam.yml @@ -0,0 +1,40 @@ +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: ovs-cni-net +spec: + config: '{ + "cniVersion": "0.3.1", + "type": "ovs", + "bridge": "br0", + "ipam": { + "type": "host-local", + "subnet": "192.168.0.0/24", + "rangeStart": "192.168.0.200", + "rangeEnd": "192.168.0.216", + "routes": [ + { "dst": "0.0.0.0/0" } + ], + "gateway": "192.168.0.1" + }, + "trunk": [ + { "id" : 42, + "ipam": { + "type": "host-local", + "subnet": "192.168.42.0/24", + "rangeStart": "192.168.42.200", + "rangeEnd": "192.168.42.216", + "gateway": "192.168.42.1" + } + }, + { "id" : 50, + "ipam": { + "type": "host-local", + "subnet": "192.168.50.0/24", + "rangeStart": "192.168.50.200", + "rangeEnd": "192.168.50.216", + "gateway": "192.168.50.1" + } + } + ] +}' diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index aae11ec19..6740fe26c 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -55,6 +55,7 @@ const ( type netConf struct { types.NetConf BrName string `json:"bridge,omitempty"` + IPAM *IPAM `json:"ipam,omitempty"` VlanTag *uint `json:"vlan"` MTU int `json:"mtu"` Trunk []*trunk `json:"trunk,omitempty"` @@ -67,6 +68,26 @@ type trunk struct { MinID *uint `json:"minID,omitempty"` MaxID *uint `json:"maxID,omitempty"` ID *uint `json:"id,omitempty"` + IPAM *IPAM `json:"ipam,omitempty"` +} + +// IPAM ipam configuration +type IPAM struct { + *Range + Type string `json:"type,omitempty"` + Routes []*types.Route `json:"routes,omitempty"` + Ranges []RangeSet `json:"ranges,omitempty"` +} + +// RangeSet ip range set +type RangeSet []Range + +// Range ip range configuration +type Range struct { + RangeStart net.IP `json:"rangeStart,omitempty"` // The first ip, inclusive + RangeEnd net.IP `json:"rangeEnd,omitempty"` // The last ip, inclusive + Subnet types.IPNet `json:"subnet"` + Gateway net.IP `json:"gateway,omitempty"` } // EnvArgs args containing common, desired mac and ovs port name @@ -118,6 +139,17 @@ func loadNetConf(bytes []byte) (*netConf, error) { return netconf, nil } +func marshalNetConf(netconf *netConf) ([]byte, error) { + var ( + data []byte + err error + ) + if data, err = json.Marshal(*netconf); err != nil { + return nil, fmt.Errorf("failed to marshal netconf: %v", err) + } + return data, nil +} + func loadFlatNetConf(configPath string) (*netConf, error) { confFiles := getOvsConfFiles() if configPath != "" { @@ -183,6 +215,94 @@ func generateRandomMac() net.HardwareAddr { return net.HardwareAddr(append(prefix, suffix...)) } +func setupTrunkIfaces(netconf *netConf, contNetns ns.NetNS, contIfaceName, contIfaceMac string) ([]*current.Result, error) { + results := make([]*current.Result, 0) + origIPAMConfig := netconf.IPAM + macAddress, err := net.ParseMAC(contIfaceMac) + if err != nil { + return nil, fmt.Errorf("failed to parse requested MAC %q: %v", contIfaceMac, err) + } + err = contNetns.Do(func(hostNetns ns.NetNS) error { + for _, trunk := range netconf.Trunk { + if trunk.IPAM == nil || trunk.IPAM.Type == "" { + continue + } + subIfName := contIfaceName + "." + fmt.Sprint(*trunk.ID) + if err := createVlanLink(contIfaceName, subIfName, *trunk.ID); err != nil { + return err + } + containerSubIfLink, err := netlink.LinkByName(subIfName) + if err != nil { + return err + } + err = assignMacToLink(containerSubIfLink, macAddress, subIfName) + if err != nil { + return err + } + netconf.IPAM = trunk.IPAM + netData, err := marshalNetConf(netconf) + if err != nil { + return err + } + var result *current.Result + r, err := ipam.ExecAdd(trunk.IPAM.Type, netData) + if err != nil { + // Invoke ipam del if err to avoid ip leak + ipam.ExecDel(trunk.IPAM.Type, netData) + return fmt.Errorf("failed to set up IPAM plugin type %q for vlan %d: %v", trunk.IPAM.Type, *trunk.ID, err) + } + // Convert whatever the IPAM result was into the current Result type + result, err = current.NewResultFromResult(r) + if err != nil { + return err + } + if len(result.IPs) == 0 { + return fmt.Errorf("IPAM plugin %q returned missing IP config for vlan %d: %v", trunk.IPAM.Type, *trunk.ID, err) + } + result.Interfaces = []*current.Interface{{ + Name: subIfName, + Mac: macAddress.String(), + Sandbox: contNetns.Path(), + }} + for _, ipc := range result.IPs { + // All addresses apply to the container interface (move from host) + ipc.Interface = current.Int(0) + } + + if err := ipam.ConfigureIface(subIfName, result); err != nil { + return err + } + results = append(results, result) + } + return nil + }) + netconf.IPAM = origIPAMConfig + if err != nil { + return nil, err + } + return results, nil +} + +func cleanupTrunkIfaces(netconf *netConf) error { + origIPAMConfig := netconf.IPAM + for _, trunk := range netconf.Trunk { + if trunk.IPAM == nil || trunk.IPAM.Type == "" { + continue + } + netconf.IPAM = trunk.IPAM + netData, err := marshalNetConf(netconf) + if err != nil { + return err + } + err = ipam.ExecDel(trunk.IPAM.Type, netData) + if err != nil { + return err + } + } + netconf.IPAM = origIPAMConfig + return nil +} + func setupVeth(contNetns ns.NetNS, contIfaceName string, requestedMac string, mtu int) (*current.Interface, *current.Interface, error) { hostIface := ¤t.Interface{} contIface := ¤t.Interface{} @@ -248,6 +368,67 @@ func assignMacToLink(link netlink.Link, mac net.HardwareAddr, name string) error return nil } +func createVlanLink(parentIfName string, subIfName string, vlanID uint) error { + if vlanID > 4094 || vlanID < 1 { + return fmt.Errorf("vlan id must be between 1-4094, received: %d", vlanID) + } + if interfaceExists(subIfName) { + return nil + } + // get the parent link to attach a vlan subinterface + parentLink, err := netlink.LinkByName(parentIfName) + if err != nil { + return fmt.Errorf("failed to find master interface %s on the host: %v", parentIfName, err) + } + vlanLink := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: subIfName, + ParentIndex: parentLink.Attrs().Index, + }, + VlanId: int(vlanID), + } + // create the subinterface + if err := netlink.LinkAdd(vlanLink); err != nil { + return fmt.Errorf("failed to create %s vlan link: %v", vlanLink.Name, err) + } + // Bring the new netlink iface up + if err := netlink.LinkSetUp(vlanLink); err != nil { + return fmt.Errorf("failed to enable %s the macvlan parent link %v", vlanLink.Name, err) + } + return nil +} + +func deleteVlanLink(parentIfName string, subIfName string, vlanID uint) error { + if !interfaceExists(subIfName) { + return nil + } + // get the parent link to attach a vlan subinterface + parentLink, err := netlink.LinkByName(parentIfName) + if err != nil { + return nil + } + vlanLink := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: subIfName, + ParentIndex: parentLink.Attrs().Index, + }, + VlanId: int(vlanID), + } + // delete the subinterface + if err := netlink.LinkDel(vlanLink); err != nil { + return fmt.Errorf("failed to delete %s vlan link: %v", vlanLink.Name, err) + } + return nil +} + +func interfaceExists(ifName string) bool { + _, err := netlink.LinkByName(ifName) + if err != nil { + return false + } + return true +} + func getBridgeName(bridgeName, ovnPort string) (string, error) { if bridgeName != "" { return bridgeName, nil @@ -411,7 +592,7 @@ func CmdAdd(args *skel.CmdArgs) error { } // run the IPAM plugin - if netconf.IPAM.Type != "" { + if netconf.IPAM != nil && netconf.IPAM.Type != "" { r, err := ipam.ExecAdd(netconf.IPAM.Type, args.StdinData) if err != nil { return fmt.Errorf("failed to set up IPAM plugin type %q: %v", netconf.IPAM.Type, err) @@ -485,6 +666,23 @@ func CmdAdd(args *skel.CmdArgs) error { } } + if len(netconf.Trunk) > 0 { + subIfResults, err := setupTrunkIfaces(netconf, contNetns, args.IfName, contIface.Mac) + if err != nil { + return err + } + ifLength := len(result.Interfaces) + for idx, subIfresult := range subIfResults { + // subIfresult has only one interface + result.Interfaces = append(result.Interfaces, subIfresult.Interfaces[0]) + ifIdxPtr := current.Int(ifLength + idx) + for ipIndex := range subIfresult.IPs { + subIfresult.IPs[ipIndex].Interface = ifIdxPtr + result.IPs = append(result.IPs, subIfresult.IPs[ipIndex]) + } + } + } + return types.PrintResult(result, netconf.CNIVersion) } @@ -570,12 +768,18 @@ func CmdDel(args *skel.CmdArgs) error { return err } - if netconf.IPAM.Type != "" { + if netconf.IPAM != nil && netconf.IPAM.Type != "" { err = ipam.ExecDel(netconf.IPAM.Type, args.StdinData) if err != nil { return err } } + if len(netconf.Trunk) > 0 { + err = cleanupTrunkIfaces(netconf) + if err != nil { + return err + } + } if args.Netns == "" { // The CNI_NETNS parameter may be empty according to version 0.4.0