Skip to content

Commit

Permalink
iptables-allow: new plugin which adds a host interface to the filter/…
Browse files Browse the repository at this point in the history
…FORWARD chain

Distros often have additional rules in the 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.  eg:

{
    "cniVersion": "0.3.1",
    "name": "bridge-thing",
    "plugins": [
      {
        "type": "bridge",
        "bridge": "cni0",
        "isGateway": true,
        "ipMasq": true,
        "ipam": {
            "type": "host-local",
            "subnet": "10.88.0.0/16",
            "routes": [
                { "dst": "0.0.0.0/0" }
            ]
        }
      },
      {
        "type": "iptables-allow"
      }
    ]
}
  • Loading branch information
dcbw committed Sep 29, 2017
1 parent e256564 commit 1619a37
Show file tree
Hide file tree
Showing 4 changed files with 462 additions and 0 deletions.
24 changes: 24 additions & 0 deletions plugins/meta/iptables-allow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# iptables-allow plugin

## Overview

This plugin creates iptables rules to allow traffic from the first host interface provided by a previous plugin.
It does not create any network interfaces and therefore does not bring connectivity by itself.
It is only useful when used in addition to other plugins.

## Operation
The following network configuration file
```
{
"name": "mytuning",
"type": "iptables-allow",
}
```
will create a new iptables chain in the `filter` table and add rules that allow
the previous plugin's host interface to send/receive traffic.

A successful result would simply be:
```
{ }
```

190 changes: 190 additions & 0 deletions plugins/meta/iptables-allow/iptables_allow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// 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"

"github.com/coreos/go-iptables/iptables"

"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"
)

// TuningConf represents the network tuning configuration.
type IptAllowConf struct {
types.NetConf
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
}

func parseConf(data []byte) (*IptAllowConf, error) {
conf := IptAllowConf{}
if err := json.Unmarshal(data, &conf); err != nil {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}

// 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 getPrivChainRules(intf string) [][]string {
var rules [][]string
rules = append(rules, []string{"-i", intf, "!", "-o", intf, "-j", "ACCEPT"})
rules = append(rules, []string{"-i", intf, "-o", intf, "-j", "ACCEPT"})
rules = append(rules, []string{"-o", intf, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-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 addRules(intf string, proto iptables.Protocol) error {
var (
err error
ipt *iptables.IPTables
exists bool
)

ipt, err = iptables.NewWithProtocol(proto)
if err != nil {
return fmt.Errorf("failed to initialize iptables helper: %v", err)
}

adminChainName := fmt.Sprintf("CNI-ADMIN-%s", intf)
privChainName := fmt.Sprintf("CNI-FORWARD-%s", intf)

var filterRules [][]string
filterRules = append(filterRules, []string{"-m", "comment", "--comment", fmt.Sprintf("CNI interface %s private rules", intf), "-j", privChainName})
filterRules = append(filterRules, []string{"-i", intf, "!", "-o", intf, "-m", "comment", "--comment", fmt.Sprintf("CNI interface %s administrator overrides", intf), "-j", adminChainName})

defer func() {
// Clean up on error
if err != nil {
ipt.DeleteChain("filter", privChainName)
ipt.DeleteChain("filter", adminChainName)
for _, rule := range filterRules {
ipt.Delete("filter", "FORWARD", rule...)
}
}
}()

if err := ensureChain(ipt, "filter", privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", adminChainName); err != nil {
return err
}

for _, rule := range filterRules {
exists, err = ipt.Exists("filter", "FORWARD", rule...)
if !exists && err == nil {
err = ipt.Insert("filter", "FORWARD", 1, rule...)
}
if err != nil {
return err
}
}

for _, rule := range getPrivChainRules(intf) {
if err := ipt.AppendUnique("filter", privChainName, rule...); err != nil {
return err
}
}

return nil
}

func cmdAdd(args *skel.CmdArgs) error {
iptConf, err := parseConf(args.StdinData)
if err != nil {
return err
}

var intfName string
if iptConf.PrevResult != nil {
for _, intf := range iptConf.PrevResult.Interfaces {
if intf.Sandbox == "" && intfName == "" {
intfName = intf.Name
break
}
}
}
if intfName == "" {
return fmt.Errorf("failed to find host interface name from PrevResult")
}

// Figure out whether we need to add rules to iptables or ip6tables or both
protos := []iptables.Protocol{}
for _, addr := range iptConf.PrevResult.IPs {
if addr.Address.IP.To4() != nil {
protos = append(protos, iptables.ProtocolIPv4)
} else {
protos = append(protos, iptables.ProtocolIPv6)
}
}

for _, proto := range protos {
if err := addRules(intfName, proto); err != nil {
return err
}
}

return types.PrintResult(iptConf.PrevResult, iptConf.CNIVersion)
}

func cmdDel(args *skel.CmdArgs) error {
// TODO: the settings are not reverted to the previous values. Reverting the
// settings is not useful when the whole container goes away but it could be
// useful in scenarios where plugins are added and removed at runtime.
return nil
}

func main() {
skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("0.3.0", version.Current()))
}
27 changes: 27 additions & 0 deletions plugins/meta/iptables-allow/iptables_allow_suite_test.go
Original file line number Diff line number Diff line change
@@ -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 TestIptablesAllow(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "iptables_allow Suite")
}
Loading

0 comments on commit 1619a37

Please sign in to comment.