Skip to content

Commit

Permalink
firewall: add nftables backend
Browse files Browse the repository at this point in the history
Resolves: containernetworking#461

Signed-off-by: Paul Greenberg <greenpau@outlook.com>
  • Loading branch information
greenpau committed Aug 1, 2020
1 parent d713ec6 commit 28c5c30
Show file tree
Hide file tree
Showing 6 changed files with 1,184 additions and 4 deletions.
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/nftables v0.0.0-20200316075819-7127d9d22474
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
github.com/mattn/go-shellwords v1.0.3
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
Expand All @@ -24,10 +25,8 @@ require (
github.com/sirupsen/logrus v1.0.6 // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 // indirect
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
)
22 changes: 22 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,20 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uH
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/nftables v0.0.0-20200316075819-7127d9d22474 h1:D6bN82zzK92ywYsE+Zjca7EHZCRZbcNTU3At7WdxQ+c=
github.com/google/nftables v0.0.0-20200316075819-7127d9d22474/go.mod h1:cfspEyr/Ap+JDIITA+N9a0ernqG0qZ4W1aqMRgDZa1g=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d h1:MFX8DxRnKMY/2M3H61iSsVbo/n3h0MWGmWNN1UViOU0=
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d/go.mod h1:QHb4k4cr1fQikUahfcRVPcEXiUgFsdIstGqlurL0XL4=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b h1:W3er9pI7mt2gOqOWzwvx20iJ8Akiqz1mUMTxU6wdvl8=
github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
Expand All @@ -49,10 +59,22 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrB
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE=
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
Expand Down
204 changes: 204 additions & 0 deletions plugins/meta/firewall/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,207 @@ of the container as shown:
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`
- `-d 10.88.0.2 -j ACCEPT`

## nftables backend rule structure

The prerequisite for the backend is the existence of `filter` table and
the existence of `FORWARD` chain in the table.

A sample standalone config list (with the file extension `.conflist`) using
`nftables` backend might look like:

```json
{
"cniVersion": "0.4.0",
"name": "podman",
"plugins": [
{
"type": "bridge",
"bridge": "cni-podman0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"ranges": [
[
{
"subnet": "192.168.100.0/24",
"gateway": "192.168.100.1"
}
]
]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
},
{
"type": "firewall",
"backend": "nftables"
}
]
}
```

Prior to the invocation of CNI `firewall` plugin, the `FORWARD` chain in `filter`
table might be configured be as follows:

```
table ip filter {
chain FORWARD { # handle 1
type filter hook forward priority filter; policy drop;
log prefix "IPv4 FORWARD drop: " flags all # handle 28
counter packets 0 bytes 0 drop # handle 29
}
}
```

Subsequently, the plugin creates "non-base chain", e.g. `cnins-3-4026543850-dummy0`
and link it to `FORWARD` chain
via [`jump` instruction](https://wiki.nftables.org/wiki-nftables/index.php/Jumping_to_chain).

```
table ip filter {
chain FORWARD { # handle 1
type filter hook forward priority filter; policy drop;
jump cnins-3-4026543850-dummy0 # handle 10
log prefix "IPv4 FORWARD drop: " flags all # handle 28
counter packets 0 bytes 0 drop # handle 29
}
chain cnins-3-4026543850-dummy0 { # handle 2
oifname "dummy0" ip daddr 192.168.100.100 ct state established,related counter packets 0 bytes 0 accept # handle 3
iifname "dummy0" ip saddr 192.168.100.100 counter packets 0 bytes 0 accept # handle 4
iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept # handle 5
}
}
```

The name of the chain is is prefixed with `CNINS-` and followed by `Dev` and `Ino`
of `Stat_t` struct. See [here](https://github.com/vishvananda/netns/blob/master/netns.go#L60)
for more information.

Generally, the testing of nftables backend of this plugin begins with defining
the data structure the plugin would receive when processing a request.
In this example, the plugin received single interface `dummy0`, with IPv4 and
IPv6 addresses.

```json
{
"name": "test",
"type": "firewall",
"backend": "nftables",
"ifName": "dummy0",
"cniVersion": "0.4.0",
"prevResult": {
"interfaces": [
{
"name": "dummy0"
}
],
"ips": [
{
"version": "4",
"address": "192.168.200.10/24",
"interface": 0
},
{
"version": "6",
"address": "2001:db8:1:2::1/64",
"interface": 0
}
]
}
}
```

Prior to running tests, the test harness does the following:

1. creates `originalNS` namespace
2. adds `dummy0` interface to `originalNS` via Netlink
3. checks that the `dummy0` interface is available in the `originalNS`
4. creates `targetNS` namespace

Upon the completion of the testing, the test harness does the following:

1. closes `originalNS` namespace
2. closes `targetNS` namespace

The tests in the harness start with `It()`.

Generally, a test contains a number of input arguments. In the case of
"installs nftables rules, checks the rules exist, then cleans up on delete using v4.0.x",
the test has the following arguments:

* container id: `dummy`
* the path to container namespace, i.e. `targetNS`
* the name of the interface
* the JSON payload containing a dummy request

The test uses the same arguments and runs the following operations in
`originalNS` namespace:

* `cmdAdd`
* `cmdCheck`
* `cmdDel`

The operations correspond to the following functions:

| **Operation** | **Function** |
| --- | --- |
| `cmdAdd` | `func (nb *nftBackend) Add(conf *FirewallNetConf, result *current.Result)` |
| `cmdCheck` | `func (nb *nftBackend) Del(conf *FirewallNetConf, result *current.Result)` |
| `cmdDel` | `func (nb *nftBackend) Check(conf *FirewallNetConf, result *current.Result)` |

The following command triggers the testing of `firewall` plugin:

```bash
sudo go test -v ./plugins/meta/firewall
```

At the outset, the test outputs dummy host and container namespaces:

```
Host Namespace: /var/run/netns/cnitest-ba94096d-68e1-90c0-5e0a-4acf3a8339cd
Container Namespace: /var/run/netns/cnitest-762bc306-9af5-5882-af95-e011590ce8d3
```

The knowing the last part of the namespace path helps inspecting namespaces
with `sudo ip netns exec` command. For example, the following command
show `nftables` tables, chains, and rules.

```bash

$ sudo ip netns exec cnitest-ba94096d-68e1-90c0-5e0a-4acf3a8339cd nft list ruleset
table ip filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump cnins-3-4026550857-dummy0
}

chain cnins-3-4026550857-dummy0 {
oifname "dummy0" ip daddr 192.168.100.100 ct state established,related counter packets 0 bytes 0 accept
iifname "dummy0" ip saddr 192.168.100.100 counter packets 0 bytes 0 accept
iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept
}
}
table ip6 filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump cnins-3-4026550857-dummy0
}

chain cnins-3-4026550857-dummy0 {
oifname "dummy0" ip6 daddr 2001:db8:100:100::1 ct state established,related counter packets 0 bytes 0 accept
iifname "dummy0" ip6 saddr 2001:db8:100:100::1 counter packets 0 bytes 0 accept
iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept
}
}
```
2 changes: 2 additions & 0 deletions plugins/meta/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ func getBackend(conf *FirewallNetConf) (FirewallBackend, error) {
switch conf.Backend {
case "iptables":
return newIptablesBackend(conf)
case "nftables":
return newNftablesBackend(conf)
case "firewalld":
return newFirewalldBackend(conf)
}
Expand Down
Loading

0 comments on commit 28c5c30

Please sign in to comment.