Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] configure vlan sub interfaces #151

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions examples/ovs-net-vlan-ipam.yml
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}'
208 changes: 206 additions & 2 deletions pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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
Expand Down Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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 := &current.Interface{}
contIface := &current.Interface{}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand Down