// Copyright 2017 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" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/containernetworking/cni/pkg/types" ) var _ = Describe("portmapping configuration", func() { netName := "testNetName" containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y" 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 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()) 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")) n, err := types.ParseCIDR("10.0.0.2/24") Expect(err).NotTo(HaveOccurred()) Expect(c.ContIPv4).To(Equal(*n)) n, err = types.ParseCIDR("2001:db8:1::2/64") Expect(err).NotTo(HaveOccurred()) Expect(c.ContIPv6).To(Equal(*n)) }) 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(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")) }) 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") Expect(err).NotTo(HaveOccurred()) 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") Expect(err).NotTo(HaveOccurred()) 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") Expect(err).NotTo(HaveOccurred()) 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") Expect(err).NotTo(HaveOccurred()) 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, })) }) }) }) } })