From d096a4df48981a0d280443eccfda5b2300d61ef8 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 29 Sep 2017 16:34:18 -0500 Subject: [PATCH] firewall: new plugin which allows a host interface to send/receive traffic Distros often have additional rules in the their iptabvles 'filter' table that do things like: -A FORWARD -j REJECT --reject-with icmp-host-prohibited docker, for example, gets around this by adding explicit rules to the filter table's FORWARD chain to allow traffic from the docker0 interface. Do that for a given host interface too, as a chained plugin. --- plugins/linux_only.txt | 12 + plugins/meta/firewall/README.md | 33 ++ plugins/meta/firewall/firewall.go | 162 ++++++++ .../meta/firewall/firewall_iptables_test.go | 368 ++++++++++++++++++ plugins/meta/firewall/firewall_suite_test.go | 27 ++ plugins/meta/firewall/iptables.go | 213 ++++++++++ 6 files changed, 815 insertions(+) create mode 100644 plugins/linux_only.txt create mode 100644 plugins/meta/firewall/README.md create mode 100644 plugins/meta/firewall/firewall.go create mode 100644 plugins/meta/firewall/firewall_iptables_test.go create mode 100644 plugins/meta/firewall/firewall_suite_test.go create mode 100644 plugins/meta/firewall/iptables.go diff --git a/plugins/linux_only.txt b/plugins/linux_only.txt new file mode 100644 index 00000000..b789fb4c --- /dev/null +++ b/plugins/linux_only.txt @@ -0,0 +1,12 @@ +plugins/ipam/dhcp +plugins/main/bridge +plugins/main/host-device +plugins/main/ipvlan +plugins/main/loopback +plugins/main/macvlan +plugins/main/ptp +plugins/main/vlan +plugins/meta/portmap +plugins/meta/tuning +plugins/meta/bandwidth +plugins/meta/firewall diff --git a/plugins/meta/firewall/README.md b/plugins/meta/firewall/README.md new file mode 100644 index 00000000..b9b83b63 --- /dev/null +++ b/plugins/meta/firewall/README.md @@ -0,0 +1,33 @@ +# firewall plugin + +## Overview + +This plugin creates firewall rules to allow traffic to/from the host network interface given by "ifName". +It does not create any network interfaces and therefore does not set up connectivity by itself. +It is only useful when used in addition to other plugins. + +## Operation +The following network configuration file +``` +{ + "name": "mynet", + "type": "firewall", + "ifName": "cni0" +} +``` + +will allow the given interface to send/receive traffic via the host. + +A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that verbatim. + +## Backends + +This plugin supports multiple firewall backends that implement the desired functionality. +Available backends include `iptables` and `firewalld` and may be selected with the `backend` key. +If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus. +If no firewalld service is found, it will fall back to iptables. + +When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic. +When the `firewalld` backend is used, the above example will place the `cni0` interface into firewalld's `trusted` zone, allowing it to send/receive traffic. + + diff --git a/plugins/meta/firewall/firewall.go b/plugins/meta/firewall/firewall.go new file mode 100644 index 00000000..a278d590 --- /dev/null +++ b/plugins/meta/firewall/firewall.go @@ -0,0 +1,162 @@ +// Copyright 2016 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. + +// This is a "meta-plugin". It reads in its own netconf, it does not create +// any network interface but just changes the network sysctl. + +package main + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" +) + +// FirewallNetConf represents the firewall configuration. +type FirewallNetConf struct { + types.NetConf + + // Backend is the firewall type to add rules to. Allowed values are + // 'iptables' and 'firewalld'. + Backend string `json:"backend"` + + // IptablesAdminChainName is an optional name to use instead of the default + // admin rules override chain name that includes the interface name. + IptablesAdminChainName string `json:"iptablesAdminChainName,omitempty"` + + // FirewalldZone is an optional firewalld zone to place the interface into. If + // the firewalld backend is used but the zone is not given, it defaults + // to 'trusted' + FirewalldZone string `json:"firewalldZone,omitempty"` + + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult *current.Result `json:"-"` +} + +type FirewallBackend interface { + Add(*FirewallNetConf) error + Del(*FirewallNetConf) error +} + +func ipString(ip net.IPNet) string { + if ip.IP.To4() == nil { + return ip.IP.String() + "/128" + } + return ip.IP.String() + "/32" +} + +func parseConf(data []byte) (*FirewallNetConf, error) { + conf := FirewallNetConf{} + if err := json.Unmarshal(data, &conf); err != nil { + return nil, fmt.Errorf("failed to load netconf: %v", err) + } + + // Default the firewalld zone to trusted + if conf.FirewalldZone == "" { + conf.FirewalldZone = "trusted" + } + + // Parse previous result. + if conf.RawPrevResult != nil { + resultBytes, err := json.Marshal(conf.RawPrevResult) + if err != nil { + return nil, fmt.Errorf("could not serialize prevResult: %v", err) + } + res, err := version.NewResult(conf.CNIVersion, resultBytes) + if err != nil { + return nil, fmt.Errorf("could not parse prevResult: %v", err) + } + conf.RawPrevResult = nil + conf.PrevResult, err = current.NewResultFromResult(res) + if err != nil { + return nil, fmt.Errorf("could not convert result to current version: %v", err) + } + } + + return &conf, nil +} + +func getBackend(conf *FirewallNetConf) (FirewallBackend, error) { + switch conf.Backend { + case "iptables": + return newIptablesBackend(conf) + case "firewalld": + return newFirewalldBackend(conf) + } + + // Default to firewalld if it's running + if isFirewalldRunning() { + return newFirewalldBackend(conf) + } + + // Otherwise iptables + return newIptablesBackend(conf) +} + +func cmdAdd(args *skel.CmdArgs) error { + conf, err := parseConf(args.StdinData) + if err != nil { + return err + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + if err := backend.Add(conf); err != nil { + return err + } + + result := conf.PrevResult + 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 + } + + backend, err := getBackend(conf) + if err != nil { + return err + } + + // Tolerate errors if the container namespace has been torn down already + containerNS, err := ns.GetNS(args.Netns) + if err == nil { + defer containerNS.Close() + } + + // Runtime errors are ignored + if err := backend.Del(conf); err != nil { + return err + } + + return nil +} + +func main() { + skel.PluginMain(cmdAdd, cmdDel, version.All) +} diff --git a/plugins/meta/firewall/firewall_iptables_test.go b/plugins/meta/firewall/firewall_iptables_test.go new file mode 100644 index 00000000..8cb1fb6f --- /dev/null +++ b/plugins/meta/firewall/firewall_iptables_test.go @@ -0,0 +1,368 @@ +// 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 ( + "encoding/json" + "fmt" + "strings" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/vishvananda/netlink" + + "github.com/coreos/go-iptables/iptables" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func findChains(chains []string) (bool, bool) { + var foundAdmin, foundPriv bool + for _, ch := range chains { + if ch == "CNI-ADMIN" { + foundAdmin = true + } else if ch == "CNI-FORWARD" { + foundPriv = true + } + } + return foundAdmin, foundPriv +} + +func findForwardJumpRules(rules []string) (bool, bool) { + var foundAdmin, foundPriv bool + for _, rule := range rules { + if strings.Contains(rule, "-j CNI-ADMIN") { + foundAdmin = true + } else if strings.Contains(rule, "-j CNI-FORWARD") { + foundPriv = true + } + } + return foundAdmin, foundPriv +} + +func findForwardAllowRules(rules []string, ip string) (bool, bool) { + var foundOne, foundTwo bool + for _, rule := range rules { + if !strings.HasSuffix(rule, "-j ACCEPT") { + continue + } + if strings.Contains(rule, fmt.Sprintf(" -s %s ", ip)) { + foundOne = true + } else if strings.Contains(rule, fmt.Sprintf(" -d %s ", ip)) && strings.Contains(rule, "RELATED,ESTABLISHED") { + foundTwo = true + } + } + return foundOne, foundTwo +} + +func getPrevResult(bytes []byte) *current.Result { + type TmpConf struct { + types.NetConf + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult *current.Result `json:"-"` + } + + conf := &TmpConf{} + err := json.Unmarshal(bytes, conf) + Expect(err).NotTo(HaveOccurred()) + if conf.RawPrevResult == nil { + return nil + } + + resultBytes, err := json.Marshal(conf.RawPrevResult) + Expect(err).NotTo(HaveOccurred()) + res, err := version.NewResult(conf.CNIVersion, resultBytes) + Expect(err).NotTo(HaveOccurred()) + prevResult, err := current.NewResultFromResult(res) + Expect(err).NotTo(HaveOccurred()) + + return prevResult +} + +func validateFullRuleset(bytes []byte) { + prevResult := getPrevResult(bytes) + + for _, ip := range prevResult.IPs { + ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Ensure chains + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, foundPriv := findChains(chains) + Expect(foundAdmin).To(Equal(true)) + Expect(foundPriv).To(Equal(true)) + + // Look for the FORWARD chain jump rules to our custom chains + rules, err := ipt.List("filter", "FORWARD") + Expect(err).NotTo(HaveOccurred()) + Expect(len(rules)).Should(BeNumerically(">", 1)) + _, foundPriv = findForwardJumpRules(rules) + Expect(foundPriv).To(Equal(true)) + + // Look for the allow rules in our custom FORWARD chain + rules, err = ipt.List("filter", "CNI-FORWARD") + Expect(err).NotTo(HaveOccurred()) + Expect(len(rules)).Should(BeNumerically(">", 1)) + foundAdmin, _ = findForwardJumpRules(rules) + Expect(foundAdmin).To(Equal(true)) + + // Look for the IP allow rules + foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address)) + Expect(foundOne).To(Equal(true)) + Expect(foundTwo).To(Equal(true)) + } +} + +func validateCleanedUp(bytes []byte) { + prevResult := getPrevResult(bytes) + + for _, ip := range prevResult.IPs { + ipt, err := iptables.NewWithProtocol(protoForIP(ip.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Our private and admin chains don't get cleaned up + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, foundPriv := findChains(chains) + Expect(foundAdmin).To(Equal(true)) + Expect(foundPriv).To(Equal(true)) + + // Look for the FORWARD chain jump rules to our custom chains + rules, err := ipt.List("filter", "FORWARD") + Expect(err).NotTo(HaveOccurred()) + _, foundPriv = findForwardJumpRules(rules) + Expect(foundPriv).To(Equal(true)) + + // Look for the allow rules in our custom FORWARD chain + rules, err = ipt.List("filter", "CNI-FORWARD") + Expect(err).NotTo(HaveOccurred()) + foundAdmin, _ = findForwardJumpRules(rules) + Expect(foundAdmin).To(Equal(true)) + + // Expect no IP address rules for this IP + foundOne, foundTwo := findForwardAllowRules(rules, ipString(ip.Address)) + Expect(foundOne).To(Equal(false)) + Expect(foundTwo).To(Equal(false)) + } +} + +var _ = Describe("firewall plugin iptables backend", func() { + var originalNS, targetNS ns.NetNS + const IFNAME string = "dummy0" + + fullConf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.3.1", + "prevResult": { + "interfaces": [ + {"name": "dummy0"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1:2::1/64", + "interface": 0 + } + ] + } + }`) + + BeforeEach(func() { + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: IFNAME, + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + }) + + It("passes prevResult through unchanged", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(2)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + Expect(result.IPs[1].Address.String()).To(Equal("2001:db8:1:2::1/64")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("installs the right iptables rules on the host", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + validateFullRuleset(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("correctly handles a custom IptablesAdminChainName", func() { + conf := []byte(`{ + "name": "test", + "type": "firewall", + "backend": "iptables", + "ifName": "dummy0", + "cniVersion": "0.3.1", + "iptablesAdminChainName": "CNI-foobar", + "prevResult": { + "interfaces": [ + {"name": "dummy0"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "interface": 0 + }, + { + "version": "6", + "address": "2001:db8:1:2::1/64", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, conf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + var ipt *iptables.IPTables + for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} { + ipt, err = iptables.NewWithProtocol(proto) + Expect(err).NotTo(HaveOccurred()) + + // Ensure custom admin chain name + chains, err := ipt.ListChains("filter") + Expect(err).NotTo(HaveOccurred()) + var foundAdmin bool + for _, ch := range chains { + if ch == "CNI-foobar" { + foundAdmin = true + } + } + Expect(foundAdmin).To(Equal(true)) + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("cleans up on delete", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: fullConf, + } + + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAddWithResult(targetNS.Path(), IFNAME, fullConf, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateFullRuleset(fullConf) + + err = testutils.CmdDelWithResult(targetNS.Path(), IFNAME, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + validateCleanedUp(fullConf) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/plugins/meta/firewall/firewall_suite_test.go b/plugins/meta/firewall/firewall_suite_test.go new file mode 100644 index 00000000..d3b10e21 --- /dev/null +++ b/plugins/meta/firewall/firewall_suite_test.go @@ -0,0 +1,27 @@ +// 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 ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestFirewall(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "firewall Suite") +} diff --git a/plugins/meta/firewall/iptables.go b/plugins/meta/firewall/iptables.go new file mode 100644 index 00000000..5f451d70 --- /dev/null +++ b/plugins/meta/firewall/iptables.go @@ -0,0 +1,213 @@ +// Copyright 2016 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. + +// This is a "meta-plugin". It reads in its own netconf, it does not create +// any network interface but just changes the network sysctl. + +package main + +import ( + "fmt" + "net" + + "github.com/coreos/go-iptables/iptables" +) + +func getPrivChainRules(ip string) [][]string { + var rules [][]string + rules = append(rules, []string{"-d", ip, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}) + rules = append(rules, []string{"-s", ip, "-j", "ACCEPT"}) + return rules +} + +func ensureChain(ipt *iptables.IPTables, table, chain string) error { + chains, err := ipt.ListChains(table) + if err != nil { + return fmt.Errorf("failed to list iptables chains: %v", err) + } + for _, ch := range chains { + if ch == chain { + return nil + } + } + + return ipt.NewChain(table, chain) +} + +func generateFilterRule(privChainName string) []string { + return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName} +} + +func generateAdminRule(adminChainName string) []string { + return []string{"-m", "comment", "--comment", "CNI firewall plugin admin overrides", "-j", adminChainName} +} + +func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) { + for _, rule := range rules { + ipt.Delete("filter", privChainName, rule...) + } +} + +func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) error { + exists, err := ipt.Exists("filter", chain, rule...) + if !exists && err == nil { + err = ipt.Insert("filter", chain, 1, rule...) + } + return err +} + +func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error { + privRule := generateFilterRule(ib.privChainName) + adminRule := generateFilterRule(ib.adminChainName) + + // Ensure our private chains exist + if err := ensureChain(ipt, "filter", ib.privChainName); err != nil { + return err + } + if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil { + return err + } + + // Ensure our filter rule exists in the forward chain + if err := ensureFirstChainRule(ipt, "FORWARD", privRule); err != nil { + return err + } + + // Ensure our admin override chain rule exists in our private chain + if err := ensureFirstChainRule(ipt, ib.privChainName, adminRule); err != nil { + return err + } + + return nil +} + +func protoForIP(ip net.IPNet) iptables.Protocol { + if ip.IP.To4() != nil { + return iptables.ProtocolIPv4 + } + return iptables.ProtocolIPv6 +} + +func (ib *iptablesBackend) addRules(conf *FirewallNetConf, ipt *iptables.IPTables, proto iptables.Protocol) error { + rules := make([][]string, 0) + for _, ip := range conf.PrevResult.IPs { + if protoForIP(ip.Address) == proto { + rules = append(rules, getPrivChainRules(ipString(ip.Address))...) + } + } + + if len(rules) > 0 { + if err := ib.setupChains(ipt); err != nil { + return err + } + + // Clean up on any errors + var err error + defer func() { + if err != nil { + cleanupRules(ipt, ib.privChainName, rules) + } + }() + + for _, rule := range rules { + err = ipt.AppendUnique("filter", ib.privChainName, rule...) + if err != nil { + return err + } + } + } + + return nil +} + +func (ib *iptablesBackend) delRules(conf *FirewallNetConf, ipt *iptables.IPTables, proto iptables.Protocol) error { + rules := make([][]string, 0) + for _, ip := range conf.PrevResult.IPs { + if protoForIP(ip.Address) == proto { + rules = append(rules, getPrivChainRules(ipString(ip.Address))...) + } + } + + if len(rules) > 0 { + cleanupRules(ipt, ib.privChainName, rules) + } + + return nil +} + +func findProtos(conf *FirewallNetConf) []iptables.Protocol { + protos := []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} + if conf.PrevResult != nil { + // If PrevResult is given, scan all IP addresses to figure out + // which IP versions to use + protos = []iptables.Protocol{} + for _, addr := range conf.PrevResult.IPs { + if addr.Address.IP.To4() != nil { + protos = append(protos, iptables.ProtocolIPv4) + } else { + protos = append(protos, iptables.ProtocolIPv6) + } + } + } + return protos +} + +type iptablesBackend struct { + protos map[iptables.Protocol]*iptables.IPTables + privChainName string + adminChainName string + ifName string +} + +// iptablesBackend implements the FirewallBackend interface +var _ FirewallBackend = &iptablesBackend{} + +func newIptablesBackend(conf *FirewallNetConf) (FirewallBackend, error) { + adminChainName := conf.IptablesAdminChainName + if adminChainName == "" { + adminChainName = "CNI-ADMIN" + } + + backend := &iptablesBackend{ + privChainName: "CNI-FORWARD", + adminChainName: adminChainName, + protos: make(map[iptables.Protocol]*iptables.IPTables), + } + + for _, proto := range []iptables.Protocol{iptables.ProtocolIPv4, iptables.ProtocolIPv6} { + ipt, err := iptables.NewWithProtocol(proto) + if err != nil { + return nil, fmt.Errorf("could not initialize iptables protocol %v: %v", proto, err) + } + backend.protos[proto] = ipt + } + + return backend, nil +} + +func (ib *iptablesBackend) Add(conf *FirewallNetConf) error { + for proto, ipt := range ib.protos { + if err := ib.addRules(conf, ipt, proto); err != nil { + return err + } + } + return nil +} + +func (ib *iptablesBackend) Del(conf *FirewallNetConf) error { + for proto, ipt := range ib.protos { + ib.delRules(conf, ipt, proto) + } + return nil +}