// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file 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. //go:build linux // +build linux package eni import ( "fmt" "net" "os" "strconv" "github.com/aws/amazon-vpc-cni-plugins/network/vpc" log "github.com/cihub/seelog" "github.com/vishvananda/netlink" ) // DeviceBusyErrMsg is the error message we get while adding a new GENEVE // interface that has destination IP, port and VNI that conflicts with an existing one. const DeviceBusyErrMsg = "device or resource busy" type Geneve struct { ENI DestinationIPAddress net.IP DestinationPort uint16 VNI uint32 Primary bool } // NewGeneve creates a new Geneve object. func NewGeneve( linkName string, destinationIPAddress net.IP, destinationPort uint16, vni string, primary bool) (*Geneve, error) { if linkName == "" { return nil, fmt.Errorf("link name cannot be empty") } if vni == "" { return nil, fmt.Errorf("VNI cannot be empty") } if destinationIPAddress == nil { return nil, fmt.Errorf("tunnel interface IP cannot be empty") } if destinationPort == 0 { return nil, fmt.Errorf("destination port number cannot be empty") } vniID, err := strconv.ParseInt(vni, 16, 32) if err != nil { return nil, fmt.Errorf("failed to parse VNI %s: %w", vni, err) } geneve := &Geneve{ ENI: ENI{ linkName: linkName, }, DestinationIPAddress: destinationIPAddress, DestinationPort: destinationPort, VNI: uint32(vniID), Primary: primary, } return geneve, nil } // AttachToLink attaches the Geneve object to an interface. func (geneve *Geneve) AttachToLink() error { la := netlink.NewLinkAttrs() la.Name = geneve.linkName la.MTU = vpc.JumboFrameMTU geneveLink := &netlink.Geneve{ LinkAttrs: la, Remote: geneve.DestinationIPAddress, Dport: geneve.DestinationPort, ID: geneve.VNI, } log.Infof("Creating Geneve interface %s: %+v", geneve.linkName, geneveLink) err := netlink.LinkAdd(geneveLink) if err != nil { // Depending on which namespace the existing GENEVE interface is in currently, // we will either get a "file exists" (default namespace) or "device or resource busy" // (custom namespace) error. if os.IsExist(err) || err.Error() == DeviceBusyErrMsg { log.Infof("Found existing Geneve interface %s.", geneve.linkName) } else { log.Errorf("Failed to add Geneve interface %s: %v", geneve.linkName, err) } return err } // Attach inner ENI object so that the link index and MAC address values will be stored as well. // This is done because we do not specify MAC address while creating the GENEVE interface and // we need to know the OS assigned MAC address so that it can be included in the plugin // execution results. if err = geneve.ENI.AttachToLink(); err != nil { log.Errorf("Failed to attach geneve object to interface %s: %v", geneve.linkName, err) return err } return nil } // DetachFromLink detaches the GENEVE object from a link. func (geneve *Geneve) DetachFromLink() error { // Delete the GENEVE link. la := netlink.NewLinkAttrs() la.Name = geneve.linkName geneveLink := &netlink.Geneve{LinkAttrs: la} log.Infof("Deleting geneve link for branch %s: %+v", geneve.linkName, geneveLink) err := netlink.LinkDel(geneveLink) if err != nil { log.Errorf("Failed to delete geneve link %s: %v", geneve.linkName, err) return err } geneve.linkIndex = 0 return nil }