| // Copyright 2020 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 main |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| |
| "github.com/vishvananda/netlink" |
| |
| "github.com/containernetworking/cni/pkg/skel" |
| "github.com/containernetworking/cni/pkg/types" |
| current "github.com/containernetworking/cni/pkg/types/100" |
| "github.com/containernetworking/cni/pkg/version" |
| "github.com/containernetworking/plugins/pkg/ns" |
| bv "github.com/containernetworking/plugins/pkg/utils/buildversion" |
| ) |
| |
| // VRFNetConf represents the vrf configuration. |
| type VRFNetConf struct { |
| types.NetConf |
| |
| // VRFName is the name of the vrf to add the interface to. |
| VRFName string `json:"vrfname"` |
| // Table is the optional name of the routing table set for the vrf |
| Table uint32 `json:"table"` |
| } |
| |
| func main() { |
| skel.PluginMainFuncs(skel.CNIFuncs{ |
| Add: cmdAdd, |
| Check: cmdCheck, |
| Del: cmdDel, |
| /* FIXME GC */ |
| /* FIXME Status */ |
| }, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf")) |
| } |
| |
| func cmdAdd(args *skel.CmdArgs) error { |
| conf, result, err := parseConf(args.StdinData) |
| if err != nil { |
| return err |
| } |
| |
| if conf.PrevResult == nil { |
| return fmt.Errorf("missing prevResult from earlier plugin") |
| } |
| |
| err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { |
| vrf, err := findVRF(conf.VRFName) |
| |
| // If the user set a tableid and the vrf is already in the namespace |
| // we check if the tableid is the same one already assigned to the vrf. |
| if err == nil && conf.Table != 0 && vrf.Table != conf.Table { |
| return fmt.Errorf("VRF %s already exist with different routing table %d", conf.VRFName, vrf.Table) |
| } |
| |
| if _, ok := err.(netlink.LinkNotFoundError); ok { |
| vrf, err = createVRF(conf.VRFName, conf.Table) |
| } |
| |
| if err != nil { |
| return err |
| } |
| |
| err = addInterface(vrf, args.IfName) |
| if err != nil { |
| return err |
| } |
| return nil |
| }) |
| if err != nil { |
| return fmt.Errorf("cmdAdd failed: %v", err) |
| } |
| |
| if result == nil { |
| result = ¤t.Result{} |
| } |
| |
| return types.PrintResult(result, conf.CNIVersion) |
| } |
| |
| func cmdDel(args *skel.CmdArgs) error { |
| conf, _, err := parseConf(args.StdinData) |
| if err != nil { |
| return err |
| } |
| err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { |
| vrf, err := findVRF(conf.VRFName) |
| if _, ok := err.(netlink.LinkNotFoundError); ok { |
| return nil |
| } |
| |
| if err != nil { |
| return err |
| } |
| |
| err = resetMaster(args.IfName) |
| if err != nil { |
| return err |
| } |
| |
| interfaces, err := assignedInterfaces(vrf) |
| if err != nil { |
| return err |
| } |
| |
| // Meaning, we are deleting the last interface assigned to the VRF |
| if len(interfaces) == 0 { |
| err = netlink.LinkDel(vrf) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| }) |
| if err != nil { |
| // if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times |
| // so don't return an error if the device is already removed. |
| // https://github.com/kubernetes/kubernetes/issues/43014#issuecomment-287164444 |
| _, ok := err.(ns.NSPathNotExistErr) |
| if ok { |
| return nil |
| } |
| return err |
| } |
| |
| if err != nil { |
| return fmt.Errorf("cmdDel failed: %v", err) |
| } |
| return nil |
| } |
| |
| func cmdCheck(args *skel.CmdArgs) error { |
| conf, _, err := parseConf(args.StdinData) |
| if err != nil { |
| return err |
| } |
| |
| // Ensure we have previous result. |
| if conf.PrevResult == nil { |
| return fmt.Errorf("missing prevResult from earlier plugin") |
| } |
| |
| err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { |
| vrf, err := findVRF(conf.VRFName) |
| if err != nil { |
| return err |
| } |
| vrfInterfaces, err := assignedInterfaces(vrf) |
| if err != nil { |
| return err |
| } |
| |
| found := false |
| for _, intf := range vrfInterfaces { |
| if intf.Attrs().Name == args.IfName { |
| found = true |
| break |
| } |
| } |
| if !found { |
| return fmt.Errorf("failed to find %s associated to vrf %s", args.IfName, conf.VRFName) |
| } |
| return nil |
| }) |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func parseConf(data []byte) (*VRFNetConf, *current.Result, error) { |
| conf := VRFNetConf{} |
| if err := json.Unmarshal(data, &conf); err != nil { |
| return nil, nil, fmt.Errorf("failed to load netconf: %v", err) |
| } |
| |
| if conf.VRFName == "" { |
| return nil, nil, fmt.Errorf("configuration is expected to have a valid vrf name") |
| } |
| |
| if conf.RawPrevResult == nil { |
| // return early if there was no previous result, which is allowed for DEL calls |
| return &conf, ¤t.Result{}, nil |
| } |
| |
| // Parse previous result. |
| var result *current.Result |
| var err error |
| if err = version.ParsePrevResult(&conf.NetConf); err != nil { |
| return nil, nil, fmt.Errorf("could not parse prevResult: %v", err) |
| } |
| |
| result, err = current.NewResultFromResult(conf.PrevResult) |
| if err != nil { |
| return nil, nil, fmt.Errorf("could not convert result to current version: %v", err) |
| } |
| |
| return &conf, result, nil |
| } |