// Copyright 2018 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 ( "fmt" "net" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" types100 "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/plugins/pkg/testutils" ) var _ = Describe("static Operations", func() { for _, ver := range testutils.AllSpecVersions { // Redefine ver inside for scope so real value is picked up by each dynamically defined It() // See Gingkgo's "Patterns for dynamically generating tests" documentation. ver := ver It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "static", "addresses": [ { "address": "10.10.0.1/24", "gateway": "10.10.0.254" }, { "address": "3ffe:ffff:0:01ff::1/64", "gateway": "3ffe:ffff:0::1" }], "routes": [ { "dst": "0.0.0.0/0" }, { "dst": "192.168.0.0/16", "gw": "10.10.5.1" }, { "dst": "3ffe:ffff:0:01ff::1/64" }], "dns": { "nameservers" : ["8.8.8.8"], "domain": "example.com", "search": [ "example.com" ] } } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP r, raw, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasIPVersion(ver) { Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) } result, err := types100.GetResult(r) Expect(err).NotTo(HaveOccurred()) // Gomega is cranky about slices with different caps Expect(*result.IPs[0]).To(Equal( types100.IPConfig{ Address: mustCIDR("10.10.0.1/24"), Gateway: net.ParseIP("10.10.0.254"), })) Expect(*result.IPs[1]).To(Equal( types100.IPConfig{ Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), Gateway: net.ParseIP("3ffe:ffff:0::1"), }, )) Expect(result.IPs).To(HaveLen(2)) Expect(result.Routes).To(Equal([]*types.Route{ {Dst: mustCIDR("0.0.0.0/0")}, {Dst: mustCIDR("192.168.0.0/16"), GW: net.ParseIP("10.10.5.1")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64")}, })) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] doesn't error when passed an unknown ID on DEL", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "static", "addresses": [ { "address": "10.10.0.1/24", "gateway": "10.10.0.254" }, { "address": "3ffe:ffff:0:01ff::1/64", "gateway": "3ffe:ffff:0::1" }], "routes": [ { "dst": "0.0.0.0/0" }, { "dst": "192.168.0.0/16", "gw": "10.10.5.1" }, { "dst": "3ffe:ffff:0:01ff::1/64" }], "dns": { "nameservers" : ["8.8.8.8"], "domain": "example.com", "search": [ "example.com" ] } } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Release the IP err := testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL, with ENV variables", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "static", "routes": [ { "dst": "0.0.0.0/0" }, { "dst": "192.168.0.0/16", "gw": "10.10.5.1" }], "dns": { "nameservers" : ["8.8.8.8"], "domain": "example.com", "search": [ "example.com" ] } } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), Args: "IP=10.10.0.1/24;GATEWAY=10.10.0.254", } // Allocate the IP r, raw, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasIPVersion(ver) { Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) } result, err := types100.GetResult(r) Expect(err).NotTo(HaveOccurred()) // Gomega is cranky about slices with different caps Expect(*result.IPs[0]).To(Equal( types100.IPConfig{ Address: mustCIDR("10.10.0.1/24"), Gateway: net.ParseIP("10.10.0.254"), })) Expect(result.IPs).To(HaveLen(1)) Expect(result.Routes).To(Equal([]*types.Route{ {Dst: mustCIDR("0.0.0.0/0")}, {Dst: mustCIDR("192.168.0.0/16"), GW: net.ParseIP("10.10.5.1")}, })) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, with ENV variables", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "static" } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), Args: "IP=10.10.0.1/24,11.11.0.1/24;GATEWAY=10.10.0.254", } // Allocate the IP r, raw, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) if !testutils.SpecVersionHasMultipleIPs(ver) { errStr := fmt.Sprintf("CNI version %s does not support more than 1 address per family", ver) Expect(err).To(MatchError(errStr)) return } Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasIPVersion(ver) { Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) } result, err := types100.GetResult(r) Expect(err).NotTo(HaveOccurred()) // Gomega is cranky about slices with different caps Expect(*result.IPs[0]).To(Equal( types100.IPConfig{ Address: mustCIDR("10.10.0.1/24"), Gateway: net.ParseIP("10.10.0.254"), })) Expect(*result.IPs[1]).To(Equal( types100.IPConfig{ Address: mustCIDR("11.11.0.1/24"), Gateway: nil, })) Expect(result.IPs).To(HaveLen(2)) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "capabilities": {"ips": true}, "ipam": { "type": "static", "routes": [ { "dst": "0.0.0.0/0", "gw": "10.10.0.254" }, { "dst": "3ffe:ffff:0:01ff::1/64", "gw": "3ffe:ffff:0::1" } ], "dns": { "nameservers" : ["8.8.8.8"], "domain": "example.com", "search": [ "example.com" ] } }, "RuntimeConfig": { "ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"] } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP r, raw, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasIPVersion(ver) { Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) } result, err := types100.GetResult(r) Expect(err).NotTo(HaveOccurred()) // Gomega is cranky about slices with different caps Expect(*result.IPs[0]).To(Equal( types100.IPConfig{ Address: mustCIDR("10.10.0.1/24"), })) Expect(*result.IPs[1]).To(Equal( types100.IPConfig{ Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), }, )) Expect(result.IPs).To(HaveLen(2)) Expect(result.Routes).To(Equal([]*types.Route{ {Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")}, })) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, from args", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "ipam": { "type": "static", "routes": [ { "dst": "0.0.0.0/0", "gw": "10.10.0.254" }, { "dst": "3ffe:ffff:0:01ff::1/64", "gw": "3ffe:ffff:0::1" } ], "dns": { "nameservers" : ["8.8.8.8"], "domain": "example.com", "search": [ "example.com" ] } }, "args": { "cni": { "ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"] } } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP r, raw, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasIPVersion(ver) { Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) } result, err := types100.GetResult(r) Expect(err).NotTo(HaveOccurred()) // Gomega is cranky about slices with different caps Expect(*result.IPs[0]).To(Equal( types100.IPConfig{ Address: mustCIDR("10.10.0.1/24"), })) Expect(*result.IPs[1]).To(Equal( types100.IPConfig{ Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), }, )) Expect(result.IPs).To(HaveLen(2)) Expect(result.Routes).To(Equal([]*types.Route{ {Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")}, })) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "capabilities": {"ips": true}, "ipam": { "type": "static", "routes": [ { "dst": "0.0.0.0/0", "gw": "10.10.0.254" }, { "dst": "3ffe:ffff:0:01ff::1/64", "gw": "3ffe:ffff:0::1" } ], "dns": { "nameservers" : ["8.8.8.8"], "domain": "example.com", "search": [ "example.com" ] } }, "RuntimeConfig": { "ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"] }, "args": { "cni": { "ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"] } } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), Args: "IP=10.10.0.3/24,11.11.0.3/24;GATEWAY=10.10.0.254", } // Allocate the IP r, raw, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasIPVersion(ver) { Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0)) } result, err := types100.GetResult(r) Expect(err).NotTo(HaveOccurred()) // only addresses in runtimeConfig configured because of its priorities Expect(*result.IPs[0]).To(Equal( types100.IPConfig{ Address: mustCIDR("10.10.0.1/24"), })) Expect(*result.IPs[1]).To(Equal( types100.IPConfig{ Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), }, )) Expect(result.IPs).To(HaveLen(2)) Expect(result.Routes).To(Equal([]*types.Route{ {Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")}, })) // Release the IP err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) }) It(fmt.Sprintf("[%s] is returning an error on missing ipam key when args are set", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0" }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), Args: "IP=10.10.0.1/24;GATEWAY=10.10.0.254", } // Allocate the IP _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).Should(MatchError("IPAM config missing 'ipam' key")) }) It(fmt.Sprintf("[%s] is returning an error on missing ipam key when runtimeConfig is set", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "foo0", "runtimeConfig": { "ips": ["10.10.0.1/24"] } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).Should(MatchError("IPAM config missing 'ipam' key")) }) It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via ipam config", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" const ipStr string = "10.10.0.1" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "bridge", "ipam": { "type": "static", "addresses": [ { "address": "%s" }] } }`, ver, ipStr) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).Should(MatchError( fmt.Sprintf("the 'address' field is expected to be in CIDR notation, got: '%s'", ipStr))) }) It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via Args", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" const ipStr string = "10.10.0.1" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "bridge", "ipam": { "type": "static", "routes": [{ "dst": "0.0.0.0/0" }] } }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), Args: fmt.Sprintf("IP=%s", ipStr), } // Allocate the IP _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).Should(MatchError( fmt.Sprintf("the 'ip' field is expected to be in CIDR notation, got: '%s'", ipStr))) }) It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via CNI_ARGS", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" const ipStr string = "10.10.0.1" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "bridge", "ipam": { "type": "static", "routes": [{ "dst": "0.0.0.0/0" }] }, "args": { "cni": { "ips" : ["%s"] } } }`, ver, ipStr) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).Should(MatchError( fmt.Sprintf("an entry in the 'ips' field is NOT in CIDR notation, got: '%s'", ipStr))) }) It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via RuntimeConfig", ver), func() { const ifname string = "eth0" const nspath string = "/some/where" const ipStr string = "10.10.0.1" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "bridge", "ipam": { "type": "static", "routes": [{ "dst": "0.0.0.0/0" }] }, "RuntimeConfig": { "ips" : ["%s"] } }`, ver, ipStr) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: nspath, IfName: ifname, StdinData: []byte(conf), } // Allocate the IP _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).Should(MatchError( fmt.Sprintf("an entry in the 'ips' field is NOT in CIDR notation, got: '%s'", ipStr))) }) } }) func mustCIDR(s string) net.IPNet { ip, n, err := net.ParseCIDR(s) n.IP = ip if err != nil { Fail(err.Error()) } return *n }