From d2e5b5decb004554547f99bbca546f49b8c8cf5b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 15 Feb 2021 10:08:24 -0600 Subject: [PATCH] portmap: increase test coverage to 1.0.0 and older spec versions Signed-off-by: Dan Williams --- plugins/meta/portmap/portmap_integ_test.go | 623 ++++++++++---------- plugins/meta/portmap/portmap_test.go | 646 +++++++++++---------- 2 files changed, 641 insertions(+), 628 deletions(-) diff --git a/plugins/meta/portmap/portmap_integ_test.go b/plugins/meta/portmap/portmap_integ_test.go index 7203f60a..3e8201e0 100644 --- a/plugins/meta/portmap/portmap_integ_test.go +++ b/plugins/meta/portmap/portmap_integ_test.go @@ -25,7 +25,7 @@ import ( "path/filepath" "github.com/containernetworking/cni/libcni" - current "github.com/containernetworking/cni/pkg/types/100" + "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" "github.com/coreos/go-iptables/iptables" @@ -37,9 +37,36 @@ import ( const TIMEOUT = 90 +func makeConfig(ver string) *libcni.NetworkConfigList { + configList, err := libcni.ConfListFromBytes([]byte(fmt.Sprintf(`{ + "cniVersion": "%s", + "name": "cni-portmap-unit-test", + "plugins": [ + { + "type": "ptp", + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "172.16.31.0/24", + "routes": [ + {"dst": "0.0.0.0/0"} + ] + } + }, + { + "type": "portmap", + "capabilities": { + "portMappings": true + } + } + ] + }`, ver))) + Expect(err).NotTo(HaveOccurred()) + return configList +} + var _ = Describe("portmap integration tests", func() { var ( - configList *libcni.NetworkConfigList cniConf *libcni.CNIConfig targetNS ns.NetNS containerPort int @@ -47,38 +74,11 @@ var _ = Describe("portmap integration tests", func() { ) BeforeEach(func() { - var err error - rawConfig := `{ - "cniVersion": "0.3.0", - "name": "cni-portmap-unit-test", - "plugins": [ - { - "type": "ptp", - "ipMasq": true, - "ipam": { - "type": "host-local", - "subnet": "172.16.31.0/24", - "routes": [ - {"dst": "0.0.0.0/0"} - ] - } - }, - { - "type": "portmap", - "capabilities": { - "portMappings": true - } - } - ] -}` - - configList, err = libcni.ConfListFromBytes([]byte(rawConfig)) - Expect(err).NotTo(HaveOccurred()) - // turn PATH in to CNI_PATH dirs := filepath.SplitList(os.Getenv("PATH")) cniConf = &libcni.CNIConfig{Path: dirs} + var err error targetNS, err = testutils.NewNS() Expect(err).NotTo(HaveOccurred()) fmt.Fprintln(GinkgoWriter, "namespace:", targetNS.Path()) @@ -90,333 +90,340 @@ var _ = Describe("portmap integration tests", func() { AfterEach(func() { session.Terminate().Wait() - if targetNS != nil { - targetNS.Close() - } + targetNS.Close() + testutils.UnmountNS(targetNS) }) - Describe("Creating an interface in a namespace with the ptp plugin", func() { - // This needs to be done using Ginkgo's asynchronous testing mode. - It("forwards a TCP port on ipv4", func(done Done) { - var err error - hostPort := rand.Intn(10000) + 1025 - runtimeConfig := libcni.RuntimeConf{ - ContainerID: fmt.Sprintf("unit-test-%d", hostPort), - NetNS: targetNS.Path(), - IfName: "eth0", - CapabilityArgs: map[string]interface{}{ - "portMappings": []map[string]interface{}{ - { - "hostPort": hostPort, - "containerPort": containerPort, - "protocol": "tcp", + for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} { + // 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 + + Describe("Creating an interface in a namespace with the ptp plugin", func() { + // This needs to be done using Ginkgo's asynchronous testing mode. + It(fmt.Sprintf("[%s] forwards a TCP port on ipv4", ver), func(done Done) { + var err error + hostPort := rand.Intn(10000) + 1025 + runtimeConfig := libcni.RuntimeConf{ + ContainerID: fmt.Sprintf("unit-test-%d", hostPort), + NetNS: targetNS.Path(), + IfName: "eth0", + CapabilityArgs: map[string]interface{}{ + "portMappings": []map[string]interface{}{ + { + "hostPort": hostPort, + "containerPort": containerPort, + "protocol": "tcp", + }, }, }, - }, - } - - // Make delete idempotent, so we can clean up on failure - netDeleted := false - deleteNetwork := func() error { - if netDeleted { - return nil } - netDeleted = true - return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig) - } + configList := makeConfig(ver) - // we'll also manually check the iptables chains - ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) - Expect(err).NotTo(HaveOccurred()) - dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name - - // Create the network - resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig) - Expect(err).NotTo(HaveOccurred()) - defer deleteNetwork() - - // Undo Docker's forwarding policy - cmd := exec.Command("iptables", "-t", "filter", - "-P", "FORWARD", "ACCEPT") - cmd.Stderr = GinkgoWriter - err = cmd.Run() - Expect(err).NotTo(HaveOccurred()) - - // Check the chain exists - _, err = ipt.List("nat", dnatChainName) - Expect(err).NotTo(HaveOccurred()) - - result, err := current.GetResult(resI) - Expect(err).NotTo(HaveOccurred()) - var contIP net.IP - - for _, ip := range result.IPs { - intfIndex := *ip.Interface - if result.Interfaces[intfIndex].Sandbox == "" { - continue + // Make delete idempotent, so we can clean up on failure + netDeleted := false + deleteNetwork := func() error { + if netDeleted { + return nil + } + netDeleted = true + return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig) } - contIP = ip.Address.IP - } - if contIP == nil { - Fail("could not determine container IP") - } - hostIP := getLocalIP() - fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n", - hostIP, hostPort, contIP, containerPort) + // we'll also manually check the iptables chains + ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) + Expect(err).NotTo(HaveOccurred()) + dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name - // dump iptables-save output for debugging - cmd = exec.Command("iptables-save") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + // Create the network + resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + defer deleteNetwork() - // dump ip routes output for debugging - cmd = exec.Command("ip", "route") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + // Undo Docker's forwarding policy + cmd := exec.Command("iptables", "-t", "filter", + "-P", "FORWARD", "ACCEPT") + cmd.Stderr = GinkgoWriter + err = cmd.Run() + Expect(err).NotTo(HaveOccurred()) - // dump ip addresses output for debugging - cmd = exec.Command("ip", "addr") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + // Check the chain exists + _, err = ipt.List("nat", dnatChainName) + Expect(err).NotTo(HaveOccurred()) - // Sanity check: verify that the container is reachable directly - contOK := testEchoServer(contIP.String(), "tcp", containerPort, "") + result, err := types100.GetResult(resI) + Expect(err).NotTo(HaveOccurred()) + var contIP net.IP - // Verify that a connection to the forwarded port works - dnatOK := testEchoServer(hostIP, "tcp", hostPort, "") + for _, ip := range result.IPs { + intfIndex := *ip.Interface + if result.Interfaces[intfIndex].Sandbox == "" { + continue + } + contIP = ip.Address.IP + } + if contIP == nil { + Fail("could not determine container IP") + } - // Verify that a connection to localhost works - snatOK := testEchoServer("127.0.0.1", "tcp", hostPort, "") + hostIP := getLocalIP() + fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n", + hostIP, hostPort, contIP, containerPort) - // verify that hairpin works - hairpinOK := testEchoServer(hostIP, "tcp", hostPort, targetNS.Path()) + // dump iptables-save output for debugging + cmd = exec.Command("iptables-save") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Cleanup - session.Terminate() - err = deleteNetwork() - Expect(err).NotTo(HaveOccurred()) + // dump ip routes output for debugging + cmd = exec.Command("ip", "route") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Verify iptables rules are gone - _, err = ipt.List("nat", dnatChainName) - Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name."))) + // dump ip addresses output for debugging + cmd = exec.Command("ip", "addr") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Check that everything succeeded *after* we clean up the network - if !contOK { - Fail("connection direct to " + contIP.String() + " failed") - } - if !dnatOK { - Fail("Connection to " + hostIP + " was not forwarded") - } - if !snatOK { - Fail("connection to 127.0.0.1 was not forwarded") - } - if !hairpinOK { - Fail("Hairpin connection failed") - } + // Sanity check: verify that the container is reachable directly + contOK := testEchoServer(contIP.String(), "tcp", containerPort, "") - close(done) - }, TIMEOUT*9) + // Verify that a connection to the forwarded port works + dnatOK := testEchoServer(hostIP, "tcp", hostPort, "") - It("forwards a UDP port on ipv4 and keep working after creating a second container with the same HostPort", func(done Done) { - var err error - hostPort := rand.Intn(10000) + 1025 - runtimeConfig := libcni.RuntimeConf{ - ContainerID: fmt.Sprintf("unit-test-%d", hostPort), - NetNS: targetNS.Path(), - IfName: "eth0", - CapabilityArgs: map[string]interface{}{ - "portMappings": []map[string]interface{}{ - { - "hostPort": hostPort, - "containerPort": containerPort, - "protocol": "udp", + // Verify that a connection to localhost works + snatOK := testEchoServer("127.0.0.1", "tcp", hostPort, "") + + // verify that hairpin works + hairpinOK := testEchoServer(hostIP, "tcp", hostPort, targetNS.Path()) + + // Cleanup + session.Terminate() + err = deleteNetwork() + Expect(err).NotTo(HaveOccurred()) + + // Verify iptables rules are gone + _, err = ipt.List("nat", dnatChainName) + Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name."))) + + // Check that everything succeeded *after* we clean up the network + if !contOK { + Fail("connection direct to " + contIP.String() + " failed") + } + if !dnatOK { + Fail("Connection to " + hostIP + " was not forwarded") + } + if !snatOK { + Fail("connection to 127.0.0.1 was not forwarded") + } + if !hairpinOK { + Fail("Hairpin connection failed") + } + + close(done) + }, TIMEOUT*9) + + It(fmt.Sprintf("[%s] forwards a UDP port on ipv4 and keep working after creating a second container with the same HostPort", ver), func(done Done) { + var err error + hostPort := rand.Intn(10000) + 1025 + runtimeConfig := libcni.RuntimeConf{ + ContainerID: fmt.Sprintf("unit-test-%d", hostPort), + NetNS: targetNS.Path(), + IfName: "eth0", + CapabilityArgs: map[string]interface{}{ + "portMappings": []map[string]interface{}{ + { + "hostPort": hostPort, + "containerPort": containerPort, + "protocol": "udp", + }, }, }, - }, - } - - // Make delete idempotent, so we can clean up on failure - netDeleted := false - deleteNetwork := func() error { - if netDeleted { - return nil } - netDeleted = true - return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig) - } + configList := makeConfig(ver) - // Create the network - resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig) - Expect(err).NotTo(HaveOccurred()) - defer deleteNetwork() - - // Undo Docker's forwarding policy - cmd := exec.Command("iptables", "-t", "filter", - "-P", "FORWARD", "ACCEPT") - cmd.Stderr = GinkgoWriter - err = cmd.Run() - Expect(err).NotTo(HaveOccurred()) - - result, err := current.GetResult(resI) - Expect(err).NotTo(HaveOccurred()) - var contIP net.IP - - for _, ip := range result.IPs { - intfIndex := *ip.Interface - if result.Interfaces[intfIndex].Sandbox == "" { - continue + // Make delete idempotent, so we can clean up on failure + netDeleted := false + deleteNetwork := func() error { + if netDeleted { + return nil + } + netDeleted = true + return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig) } - contIP = ip.Address.IP - } - if contIP == nil { - Fail("could not determine container IP") - } - hostIP := getLocalIP() - fmt.Fprintf(GinkgoWriter, "First container hostIP: %s:%d, contIP: %s:%d\n", - hostIP, hostPort, contIP, containerPort) + // Create the network + resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig) + Expect(err).NotTo(HaveOccurred()) + defer deleteNetwork() - // dump iptables-save output for debugging - cmd = exec.Command("iptables-save") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + // Undo Docker's forwarding policy + cmd := exec.Command("iptables", "-t", "filter", + "-P", "FORWARD", "ACCEPT") + cmd.Stderr = GinkgoWriter + err = cmd.Run() + Expect(err).NotTo(HaveOccurred()) - // dump ip routes output for debugging - cmd = exec.Command("ip", "route") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + result, err := types100.GetResult(resI) + Expect(err).NotTo(HaveOccurred()) + var contIP net.IP - // dump ip addresses output for debugging - cmd = exec.Command("ip", "addr") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + for _, ip := range result.IPs { + intfIndex := *ip.Interface + if result.Interfaces[intfIndex].Sandbox == "" { + continue + } + contIP = ip.Address.IP + } + if contIP == nil { + Fail("could not determine container IP") + } - // Sanity check: verify that the container is reachable directly - fmt.Fprintln(GinkgoWriter, "Connect to container:", contIP.String(), containerPort) - contOK := testEchoServer(contIP.String(), "udp", containerPort, "") + hostIP := getLocalIP() + fmt.Fprintf(GinkgoWriter, "First container hostIP: %s:%d, contIP: %s:%d\n", + hostIP, hostPort, contIP, containerPort) - // Verify that a connection to the forwarded port works - fmt.Fprintln(GinkgoWriter, "Connect to host:", hostIP, hostPort) - dnatOK := testEchoServer(hostIP, "udp", hostPort, "") + // dump iptables-save output for debugging + cmd = exec.Command("iptables-save") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Cleanup - session.Terminate() - err = deleteNetwork() - Expect(err).NotTo(HaveOccurred()) + // dump ip routes output for debugging + cmd = exec.Command("ip", "route") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Check that everything succeeded *after* we clean up the network - if !contOK { - Fail("connection direct to " + contIP.String() + " failed") - } - if !dnatOK { - Fail("Connection to " + hostIP + " was not forwarded") - } - // Create a second container - targetNS2, err := testutils.NewNS() - Expect(err).NotTo(HaveOccurred()) - fmt.Fprintln(GinkgoWriter, "namespace:", targetNS2.Path()) + // dump ip addresses output for debugging + cmd = exec.Command("ip", "addr") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Start an echo server and get the port - containerPort, session2, err := StartEchoServerInNamespace(targetNS2) - Expect(err).NotTo(HaveOccurred()) + // Sanity check: verify that the container is reachable directly + fmt.Fprintln(GinkgoWriter, "Connect to container:", contIP.String(), containerPort) + contOK := testEchoServer(contIP.String(), "udp", containerPort, "") - runtimeConfig2 := libcni.RuntimeConf{ - ContainerID: fmt.Sprintf("unit-test2-%d", hostPort), - NetNS: targetNS2.Path(), - IfName: "eth0", - CapabilityArgs: map[string]interface{}{ - "portMappings": []map[string]interface{}{ - { - "hostPort": hostPort, - "containerPort": containerPort, - "protocol": "udp", + // Verify that a connection to the forwarded port works + fmt.Fprintln(GinkgoWriter, "Connect to host:", hostIP, hostPort) + dnatOK := testEchoServer(hostIP, "udp", hostPort, "") + + // Cleanup + session.Terminate() + err = deleteNetwork() + Expect(err).NotTo(HaveOccurred()) + + // Check that everything succeeded *after* we clean up the network + if !contOK { + Fail("connection direct to " + contIP.String() + " failed") + } + if !dnatOK { + Fail("Connection to " + hostIP + " was not forwarded") + } + // Create a second container + targetNS2, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + fmt.Fprintln(GinkgoWriter, "namespace:", targetNS2.Path()) + + // Start an echo server and get the port + containerPort, session2, err := StartEchoServerInNamespace(targetNS2) + Expect(err).NotTo(HaveOccurred()) + + runtimeConfig2 := libcni.RuntimeConf{ + ContainerID: fmt.Sprintf("unit-test2-%d", hostPort), + NetNS: targetNS2.Path(), + IfName: "eth0", + CapabilityArgs: map[string]interface{}{ + "portMappings": []map[string]interface{}{ + { + "hostPort": hostPort, + "containerPort": containerPort, + "protocol": "udp", + }, }, }, - }, - } - - // Make delete idempotent, so we can clean up on failure - net2Deleted := false - deleteNetwork2 := func() error { - if net2Deleted { - return nil } - net2Deleted = true - return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig2) - } - // Create the network - resI2, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig2) - Expect(err).NotTo(HaveOccurred()) - defer deleteNetwork2() - - result2, err := current.GetResult(resI2) - Expect(err).NotTo(HaveOccurred()) - var contIP2 net.IP - - for _, ip := range result2.IPs { - intfIndex := *ip.Interface - if result2.Interfaces[intfIndex].Sandbox == "" { - continue + // Make delete idempotent, so we can clean up on failure + net2Deleted := false + deleteNetwork2 := func() error { + if net2Deleted { + return nil + } + net2Deleted = true + return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig2) } - contIP2 = ip.Address.IP - } - if contIP2 == nil { - Fail("could not determine container IP") - } - fmt.Fprintf(GinkgoWriter, "Second container: hostIP: %s:%d, contIP: %s:%d\n", - hostIP, hostPort, contIP2, containerPort) + // Create the network + resI2, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig2) + Expect(err).NotTo(HaveOccurred()) + defer deleteNetwork2() - // dump iptables-save output for debugging - cmd = exec.Command("iptables-save") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + result2, err := types100.GetResult(resI2) + Expect(err).NotTo(HaveOccurred()) + var contIP2 net.IP - // dump ip routes output for debugging - cmd = exec.Command("ip", "route") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + for _, ip := range result2.IPs { + intfIndex := *ip.Interface + if result2.Interfaces[intfIndex].Sandbox == "" { + continue + } + contIP2 = ip.Address.IP + } + if contIP2 == nil { + Fail("could not determine container IP") + } - // dump ip addresses output for debugging - cmd = exec.Command("ip", "addr") - cmd.Stderr = GinkgoWriter - cmd.Stdout = GinkgoWriter - Expect(cmd.Run()).To(Succeed()) + fmt.Fprintf(GinkgoWriter, "Second container: hostIP: %s:%d, contIP: %s:%d\n", + hostIP, hostPort, contIP2, containerPort) - // Sanity check: verify that the container is reachable directly - fmt.Fprintln(GinkgoWriter, "Connect to container:", contIP2.String(), containerPort) - cont2OK := testEchoServer(contIP2.String(), "udp", containerPort, "") + // dump iptables-save output for debugging + cmd = exec.Command("iptables-save") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Verify that a connection to the forwarded port works - fmt.Fprintln(GinkgoWriter, "Connect to host:", hostIP, hostPort) - dnat2OK := testEchoServer(hostIP, "udp", hostPort, "") + // dump ip routes output for debugging + cmd = exec.Command("ip", "route") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Cleanup - session2.Terminate() - err = deleteNetwork2() - Expect(err).NotTo(HaveOccurred()) + // dump ip addresses output for debugging + cmd = exec.Command("ip", "addr") + cmd.Stderr = GinkgoWriter + cmd.Stdout = GinkgoWriter + Expect(cmd.Run()).To(Succeed()) - // Check that everything succeeded *after* we clean up the network - if !cont2OK { - Fail("connection direct to " + contIP2.String() + " failed") - } - if !dnat2OK { - Fail("Connection to " + hostIP + " was not forwarded") - } + // Sanity check: verify that the container is reachable directly + fmt.Fprintln(GinkgoWriter, "Connect to container:", contIP2.String(), containerPort) + cont2OK := testEchoServer(contIP2.String(), "udp", containerPort, "") - close(done) - }, TIMEOUT*9) - }) + // Verify that a connection to the forwarded port works + fmt.Fprintln(GinkgoWriter, "Connect to host:", hostIP, hostPort) + dnat2OK := testEchoServer(hostIP, "udp", hostPort, "") + + // Cleanup + session2.Terminate() + err = deleteNetwork2() + Expect(err).NotTo(HaveOccurred()) + + // Check that everything succeeded *after* we clean up the network + if !cont2OK { + Fail("connection direct to " + contIP2.String() + " failed") + } + if !dnat2OK { + Fail("Connection to " + hostIP + " was not forwarded") + } + + close(done) + }, TIMEOUT*9) + }) + } }) // testEchoServer returns true if we found an echo server on the port diff --git a/plugins/meta/portmap/portmap_test.go b/plugins/meta/portmap/portmap_test.go index 12df96c5..dd68391d 100644 --- a/plugins/meta/portmap/portmap_test.go +++ b/plugins/meta/portmap/portmap_test.go @@ -27,336 +27,342 @@ var _ = Describe("portmapping configuration", func() { netName := "testNetName" containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y" - Context("config parsing", func() { - It("Correctly parses an ADD config", func() { - configBytes := []byte(`{ - "name": "test", - "type": "portmap", - "cniVersion": "0.3.1", - "runtimeConfig": { - "portMappings": [ - { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}, - { "hostPort": 8081, "containerPort": 81, "protocol": "udp"} - ] - }, - "snat": false, - "conditionsV4": ["a", "b"], - "conditionsV6": ["c", "d"], - "prevResult": { - "interfaces": [ - {"name": "host"}, - {"name": "container", "sandbox":"netns"} - ], - "ips": [ - { - "version": "4", - "address": "10.0.0.1/24", - "gateway": "10.0.0.1", - "interface": 0 - }, - { - "version": "6", - "address": "2001:db8:1::2/64", - "gateway": "2001:db8:1::1", - "interface": 1 - }, - { - "version": "4", - "address": "10.0.0.2/24", - "gateway": "10.0.0.1", - "interface": 1 - } - ] - } -}`) - c, _, err := parseConfig(configBytes, "container") - Expect(err).NotTo(HaveOccurred()) - Expect(c.CNIVersion).To(Equal("0.3.1")) - Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) - Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"})) - fvar := false - Expect(c.SNAT).To(Equal(&fvar)) - Expect(c.Name).To(Equal("test")) + for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} { + // 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 - n, err := types.ParseCIDR("10.0.0.2/24") - Expect(c.ContIPv4).To(Equal(*n)) - n, err = types.ParseCIDR("2001:db8:1::2/64") - Expect(c.ContIPv6).To(Equal(*n)) - }) - - It("Correctly parses a DEL config", func() { - // When called with DEL, neither runtimeConfig nor prevResult may be specified - configBytes := []byte(`{ - "name": "test", - "type": "portmap", - "cniVersion": "0.3.1", - "snat": false, - "conditionsV4": ["a", "b"], - "conditionsV6": ["c", "d"] -}`) - c, _, err := parseConfig(configBytes, "container") - Expect(err).NotTo(HaveOccurred()) - Expect(c.CNIVersion).To(Equal("0.3.1")) - Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) - Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"})) - fvar := false - Expect(c.SNAT).To(Equal(&fvar)) - Expect(c.Name).To(Equal("test")) - }) - - It("fails with invalid mappings", func() { - configBytes := []byte(`{ - "name": "test", - "type": "portmap", - "cniVersion": "0.3.1", - "snat": false, - "conditionsV4": ["a", "b"], - "conditionsV6": ["c", "d"], - "runtimeConfig": { - "portMappings": [ - { "hostPort": 0, "containerPort": 80, "protocol": "tcp"} - ] - } -}`) - _, _, err := parseConfig(configBytes, "container") - Expect(err).To(MatchError("Invalid host port number: 0")) - }) - - It("Does not fail on missing prevResult interface index", func() { - configBytes := []byte(`{ - "name": "test", - "type": "portmap", - "cniVersion": "0.3.1", - "runtimeConfig": { - "portMappings": [ - { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"} - ] - }, - "conditionsV4": ["a", "b"], - "prevResult": { - "interfaces": [ - {"name": "host"} - ], - "ips": [ - { - "version": "4", - "address": "10.0.0.1/24", - "gateway": "10.0.0.1" - } - ] - } -}`) - _, _, err := parseConfig(configBytes, "container") - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Describe("Generating chains", func() { - Context("for DNAT", func() { - It("generates a correct standard container chain", func() { - ch := genDnatChain(netName, containerID) - - Expect(ch).To(Equal(chain{ - table: "nat", - name: "CNI-DN-bfd599665540dd91d5d28", - entryChains: []string{TopLevelDNATChainName}, - })) - configBytes := []byte(`{ - "name": "test", - "type": "portmap", - "cniVersion": "0.3.1", - "runtimeConfig": { - "portMappings": [ - { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}, - { "hostPort": 8081, "containerPort": 80, "protocol": "tcp"}, - { "hostPort": 8080, "containerPort": 81, "protocol": "udp"}, - { "hostPort": 8082, "containerPort": 82, "protocol": "udp"}, - { "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"}, - { "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"}, - { "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"}, - { "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"} - ] - }, - "snat": true, - "conditionsV4": ["a", "b"], - "conditionsV6": ["c", "d"] -}`) - - conf, _, err := parseConfig(configBytes, "foo") + Context("config parsing", func() { + It(fmt.Sprintf("[%s] correctly parses an ADD config", ver), func() { + configBytes := []byte(fmt.Sprintf(`{ + "name": "test", + "type": "portmap", + "cniVersion": "%s", + "runtimeConfig": { + "portMappings": [ + { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}, + { "hostPort": 8081, "containerPort": 81, "protocol": "udp"} + ] + }, + "snat": false, + "conditionsV4": ["a", "b"], + "conditionsV6": ["c", "d"], + "prevResult": { + "interfaces": [ + {"name": "host"}, + {"name": "container", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.1/24", + "gateway": "10.0.0.1", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1::2/64", + "gateway": "2001:db8:1::1", + "interface": 1 + }, + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 1 + } + ] + } + }`, ver)) + c, _, err := parseConfig(configBytes, "container") Expect(err).NotTo(HaveOccurred()) - conf.ContainerID = containerID - - ch = genDnatChain(conf.Name, containerID) - Expect(ch).To(Equal(chain{ - table: "nat", - name: "CNI-DN-67e92b96e692a494b6b85", - entryChains: []string{"CNI-HOSTPORT-DNAT"}, - })) - - n, err := types.ParseCIDR("10.0.0.2/24") - fillDnatRules(&ch, conf, *n) - - Expect(ch.entryRules).To(Equal([][]string{ - {"-m", "comment", "--comment", - fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID), - "-m", "multiport", - "-p", "tcp", - "--destination-ports", "8080,8081,8083,8084,8085,8086", - "a", "b"}, - {"-m", "comment", "--comment", - fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID), - "-m", "multiport", - "-p", "udp", - "--destination-ports", "8080,8082", - "a", "b"}, - })) - - Expect(ch.rules).To(Equal([][]string{ - // tcp rules and not hostIP - {"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, - {"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, - // udp rules and not hostIP - {"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"}, - {"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"}, - // tcp rules and hostIP - {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"}, - // tcp rules and hostIP = "0.0.0.0" - {"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"}, - })) - - ch.rules = nil - ch.entryRules = nil - - n, err = types.ParseCIDR("2001:db8::2/64") - fillDnatRules(&ch, conf, *n) - - Expect(ch.rules).To(Equal([][]string{ - // tcp rules and not hostIP - {"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"}, - {"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"}, - // udp rules and not hostIP - {"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"}, - {"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"}, - // tcp rules and hostIP - {"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"}, - // tcp rules and hostIP = "::" - {"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, - {"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"}, - })) - - // Disable snat, generate rules - ch.rules = nil - ch.entryRules = nil + Expect(c.CNIVersion).To(Equal(ver)) + Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) + Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"})) fvar := false - conf.SNAT = &fvar + Expect(c.SNAT).To(Equal(&fvar)) + Expect(c.Name).To(Equal("test")) - n, err = types.ParseCIDR("10.0.0.2/24") - fillDnatRules(&ch, conf, *n) - Expect(ch.rules).To(Equal([][]string{ - {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, - {"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, - {"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"}, - {"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"}, - {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"}, - {"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"}, - })) - }) - - It("generates a correct chain with external mark", func() { - ch := genDnatChain(netName, containerID) - - Expect(ch).To(Equal(chain{ - table: "nat", - name: "CNI-DN-bfd599665540dd91d5d28", - entryChains: []string{TopLevelDNATChainName}, - })) - configBytes := []byte(`{ - "name": "test", - "type": "portmap", - "cniVersion": "0.3.1", - "runtimeConfig": { - "portMappings": [ - { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"} - ] - }, - "externalSetMarkChain": "PLZ-SET-MARK", - "conditionsV4": ["a", "b"], - "conditionsV6": ["c", "d"] -}`) - - conf, _, err := parseConfig(configBytes, "foo") - Expect(err).NotTo(HaveOccurred()) - conf.ContainerID = containerID - - ch = genDnatChain(conf.Name, containerID) n, err := types.ParseCIDR("10.0.0.2/24") - fillDnatRules(&ch, conf, *n) - Expect(ch.rules).To(Equal([][]string{ - {"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"}, - {"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"}, - {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, - })) + Expect(c.ContIPv4).To(Equal(*n)) + n, err = types.ParseCIDR("2001:db8:1::2/64") + Expect(c.ContIPv6).To(Equal(*n)) }) - It("generates a correct top-level chain", func() { - ch := genToplevelDnatChain() - - Expect(ch).To(Equal(chain{ - table: "nat", - name: "CNI-HOSTPORT-DNAT", - entryChains: []string{"PREROUTING", "OUTPUT"}, - entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}}, - })) + It(fmt.Sprintf("[%s] correctly parses a DEL config", ver), func() { + // When called with DEL, neither runtimeConfig nor prevResult may be specified + configBytes := []byte(fmt.Sprintf(`{ + "name": "test", + "type": "portmap", + "cniVersion": "%s", + "snat": false, + "conditionsV4": ["a", "b"], + "conditionsV6": ["c", "d"] + }`, ver)) + c, _, err := parseConfig(configBytes, "container") + Expect(err).NotTo(HaveOccurred()) + Expect(c.CNIVersion).To(Equal(ver)) + Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) + Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"})) + fvar := false + Expect(c.SNAT).To(Equal(&fvar)) + Expect(c.Name).To(Equal("test")) }) - It("generates the correct mark chains", func() { - masqBit := 5 - ch := genSetMarkChain(masqBit) - Expect(ch).To(Equal(chain{ - table: "nat", - name: "CNI-HOSTPORT-SETMARK", - rules: [][]string{{ - "-m", "comment", - "--comment", "CNI portfwd masquerade mark", - "-j", "MARK", - "--set-xmark", "0x20/0x20", - }}, - })) + It(fmt.Sprintf("[%s] fails with invalid mappings", ver), func() { + configBytes := []byte(fmt.Sprintf(`{ + "name": "test", + "type": "portmap", + "cniVersion": "%s", + "snat": false, + "conditionsV4": ["a", "b"], + "conditionsV6": ["c", "d"], + "runtimeConfig": { + "portMappings": [ + { "hostPort": 0, "containerPort": 80, "protocol": "tcp"} + ] + } + }`, ver)) + _, _, err := parseConfig(configBytes, "container") + Expect(err).To(MatchError("Invalid host port number: 0")) + }) - ch = genMarkMasqChain(masqBit) - Expect(ch).To(Equal(chain{ - table: "nat", - name: "CNI-HOSTPORT-MASQ", - entryChains: []string{"POSTROUTING"}, - entryRules: [][]string{{ - "-m", "comment", - "--comment", "CNI portfwd requiring masquerade", - }}, - rules: [][]string{{ - "-m", "mark", - "--mark", "0x20/0x20", - "-j", "MASQUERADE", - }}, - prependEntry: true, - })) + It(fmt.Sprintf("[%s] does not fail on missing prevResult interface index", ver), func() { + configBytes := []byte(fmt.Sprintf(`{ + "name": "test", + "type": "portmap", + "cniVersion": "%s", + "runtimeConfig": { + "portMappings": [ + { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"} + ] + }, + "conditionsV4": ["a", "b"], + "prevResult": { + "interfaces": [ + {"name": "host"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.1/24", + "gateway": "10.0.0.1" + } + ] + } + }`, ver)) + _, _, err := parseConfig(configBytes, "container") + Expect(err).NotTo(HaveOccurred()) }) }) - }) + + Describe("Generating chains", func() { + Context("for DNAT", func() { + It(fmt.Sprintf("[%s] generates a correct standard container chain", ver), func() { + ch := genDnatChain(netName, containerID) + + Expect(ch).To(Equal(chain{ + table: "nat", + name: "CNI-DN-bfd599665540dd91d5d28", + entryChains: []string{TopLevelDNATChainName}, + })) + configBytes := []byte(fmt.Sprintf(`{ + "name": "test", + "type": "portmap", + "cniVersion": "%s", + "runtimeConfig": { + "portMappings": [ + { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}, + { "hostPort": 8081, "containerPort": 80, "protocol": "tcp"}, + { "hostPort": 8080, "containerPort": 81, "protocol": "udp"}, + { "hostPort": 8082, "containerPort": 82, "protocol": "udp"}, + { "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"}, + { "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"}, + { "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"}, + { "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"} + ] + }, + "snat": true, + "conditionsV4": ["a", "b"], + "conditionsV6": ["c", "d"] + }`, ver)) + + conf, _, err := parseConfig(configBytes, "foo") + Expect(err).NotTo(HaveOccurred()) + conf.ContainerID = containerID + + ch = genDnatChain(conf.Name, containerID) + Expect(ch).To(Equal(chain{ + table: "nat", + name: "CNI-DN-67e92b96e692a494b6b85", + entryChains: []string{"CNI-HOSTPORT-DNAT"}, + })) + + n, err := types.ParseCIDR("10.0.0.2/24") + fillDnatRules(&ch, conf, *n) + + Expect(ch.entryRules).To(Equal([][]string{ + {"-m", "comment", "--comment", + fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID), + "-m", "multiport", + "-p", "tcp", + "--destination-ports", "8080,8081,8083,8084,8085,8086", + "a", "b"}, + {"-m", "comment", "--comment", + fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID), + "-m", "multiport", + "-p", "udp", + "--destination-ports", "8080,8082", + "a", "b"}, + })) + + Expect(ch.rules).To(Equal([][]string{ + // tcp rules and not hostIP + {"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, + {"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, + // udp rules and not hostIP + {"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"}, + {"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"}, + // tcp rules and hostIP + {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"}, + // tcp rules and hostIP = "0.0.0.0" + {"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"}, + })) + + ch.rules = nil + ch.entryRules = nil + + n, err = types.ParseCIDR("2001:db8::2/64") + fillDnatRules(&ch, conf, *n) + + Expect(ch.rules).To(Equal([][]string{ + // tcp rules and not hostIP + {"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"}, + {"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"}, + // udp rules and not hostIP + {"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"}, + {"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"}, + // tcp rules and hostIP + {"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"}, + // tcp rules and hostIP = "::" + {"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"}, + {"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"}, + })) + + // Disable snat, generate rules + ch.rules = nil + ch.entryRules = nil + fvar := false + conf.SNAT = &fvar + + n, err = types.ParseCIDR("10.0.0.2/24") + fillDnatRules(&ch, conf, *n) + Expect(ch.rules).To(Equal([][]string{ + {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, + {"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, + {"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"}, + {"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"}, + {"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"}, + {"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"}, + })) + }) + + It(fmt.Sprintf("[%s] generates a correct chain with external mark", ver), func() { + ch := genDnatChain(netName, containerID) + + Expect(ch).To(Equal(chain{ + table: "nat", + name: "CNI-DN-bfd599665540dd91d5d28", + entryChains: []string{TopLevelDNATChainName}, + })) + configBytes := []byte(fmt.Sprintf(`{ + "name": "test", + "type": "portmap", + "cniVersion": "%s", + "runtimeConfig": { + "portMappings": [ + { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"} + ] + }, + "externalSetMarkChain": "PLZ-SET-MARK", + "conditionsV4": ["a", "b"], + "conditionsV6": ["c", "d"] + }`, ver)) + + conf, _, err := parseConfig(configBytes, "foo") + Expect(err).NotTo(HaveOccurred()) + conf.ContainerID = containerID + + ch = genDnatChain(conf.Name, containerID) + n, err := types.ParseCIDR("10.0.0.2/24") + fillDnatRules(&ch, conf, *n) + Expect(ch.rules).To(Equal([][]string{ + {"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"}, + {"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"}, + {"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"}, + })) + }) + + It(fmt.Sprintf("[%s] generates a correct top-level chain", ver), func() { + ch := genToplevelDnatChain() + + Expect(ch).To(Equal(chain{ + table: "nat", + name: "CNI-HOSTPORT-DNAT", + entryChains: []string{"PREROUTING", "OUTPUT"}, + entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}}, + })) + }) + + It(fmt.Sprintf("[%s] generates the correct mark chains", ver), func() { + masqBit := 5 + ch := genSetMarkChain(masqBit) + Expect(ch).To(Equal(chain{ + table: "nat", + name: "CNI-HOSTPORT-SETMARK", + rules: [][]string{{ + "-m", "comment", + "--comment", "CNI portfwd masquerade mark", + "-j", "MARK", + "--set-xmark", "0x20/0x20", + }}, + })) + + ch = genMarkMasqChain(masqBit) + Expect(ch).To(Equal(chain{ + table: "nat", + name: "CNI-HOSTPORT-MASQ", + entryChains: []string{"POSTROUTING"}, + entryRules: [][]string{{ + "-m", "comment", + "--comment", "CNI portfwd requiring masquerade", + }}, + rules: [][]string{{ + "-m", "mark", + "--mark", "0x20/0x20", + "-j", "MASQUERADE", + }}, + prependEntry: true, + })) + }) + }) + }) + } })